Browse Source

连接后端数据库,大气采样地图添加乡镇划分线,大气采样按体积和重量分类计算展示

yes-yes-yes-k 4 tháng trước cách đây
mục cha
commit
9d40462091
86 tập tin đã thay đổi với 6670 bổ sung4597 xóa
  1. 3 2
      .env
  2. 24 7
      components.d.ts
  3. 24 609
      package-lock.json
  4. 1 1
      package.json
  5. 5 0
      public/data/韶关市乡镇划分图.geojson
  6. BIN
      public/images/仁化县.png
  7. BIN
      public/农业化肥采集.png
  8. BIN
      public/农业投入品使用情况.png
  9. BIN
      public/农业投入品样品采集.png
  10. BIN
      public/农业投入品用量计算方法.png
  11. BIN
      public/农业投入品重金属输入通量计算方法.png
  12. BIN
      public/农业投入品镉含量.png
  13. BIN
      public/农田土壤重金属主要的输入输出途径.png
  14. BIN
      public/农膜采集.png
  15. BIN
      public/农药采集.png
  16. BIN
      public/各农业投入品测试结果.png
  17. BIN
      public/大气干湿沉降示意图.png
  18. BIN
      public/干湿沉降收集装置.png
  19. BIN
      public/有机肥采集.png
  20. 0 1
      src/App.vue
  21. BIN
      src/assets/bg/atmospheric_deposition.png
  22. BIN
      src/assets/login-bg.png
  23. BIN
      src/assets/right-bg.png
  24. BIN
      src/assets/samplingequipment1.png
  25. BIN
      src/assets/samplingequipment2.png
  26. BIN
      src/assets/samplingequipment3.png
  27. BIN
      src/assets/samplingsite1.png
  28. BIN
      src/assets/samplingsite2.png
  29. BIN
      src/assets/samplingsite3.png
  30. 0 0
      src/components/atmpollution/airSampleTencentMap.vue
  31. 487 0
      src/components/atmpollution/airsampleChart.vue
  32. 456 0
      src/components/atmpollution/airsampleLine.vue
  33. 0 0
      src/components/atmpollution/atmCompanytencentMap.vue
  34. 219 0
      src/components/atmpollution/atmcompanyline.vue
  35. 251 0
      src/components/atmpollution/atmcompanymap.vue
  36. 377 0
      src/components/atmpollution/atmsamplemap.vue
  37. 392 0
      src/components/atmpollution/heavyMetalEnterprisechart.vue
  38. 213 0
      src/components/irrpollution/crossSectionSamplelineData.vue
  39. 262 0
      src/components/irrpollution/crossSetionData1.vue
  40. 79 50
      src/components/irrpollution/crossSetionData2.vue
  41. 85 13
      src/components/irrpollution/crossSetionTencentmap.vue
  42. 84 56
      src/components/irrpollution/crosssectionmap.vue
  43. 282 0
      src/components/irrpollution/irrwatermap.vue
  44. 0 0
      src/components/irrpollution/riverwaterassay.vue
  45. 0 0
      src/components/irrpollution/tencentMapView.vue
  46. 0 0
      src/components/irrpollution/waterassaydata1.vue
  47. 66 53
      src/components/irrpollution/waterassaydata2.vue
  48. 0 0
      src/components/irrpollution/waterassaydata3.vue
  49. 0 0
      src/components/irrpollution/waterassaydata4.vue
  50. 38 38
      src/components/irrpollution/waterdataline.vue
  51. 167 212
      src/components/layout/AppLayout.vue
  52. 0 1
      src/components/layout/isCollapse.ts
  53. 2 2
      src/components/layout/menuItems.ts
  54. 7 7
      src/router/index.ts
  55. 1 0
      src/views/AboutView.vue
  56. 237 0
      src/views/User/HmOutFlux/agriInput/farmInputSamplingDesc.vue
  57. 418 51
      src/views/User/HmOutFlux/agriInput/prodInputFlux.vue
  58. 0 25
      src/views/User/HmOutFlux/agriInput/samplingDesc2.vue
  59. 186 0
      src/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue
  60. 6 6
      src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue
  61. 0 351
      src/views/User/HmOutFlux/atmosDeposition/airsampleChart.vue
  62. 0 300
      src/views/User/HmOutFlux/atmosDeposition/airsampleLine.vue
  63. 0 218
      src/views/User/HmOutFlux/atmosDeposition/atmcompanyline.vue
  64. 0 366
      src/views/User/HmOutFlux/atmosDeposition/atmsamplemap.vue
  65. 5 8
      src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue
  66. 0 250
      src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprisechart.vue
  67. 0 25
      src/views/User/HmOutFlux/atmosDeposition/samplingDesc3.vue
  68. 5 5
      src/views/User/HmOutFlux/irrigationWater/crossSection.vue
  69. 0 139
      src/views/User/HmOutFlux/irrigationWater/crossSectionSamplelineData.vue
  70. 0 217
      src/views/User/HmOutFlux/irrigationWater/crossSetionData1.vue
  71. 0 272
      src/views/User/HmOutFlux/irrigationWater/crosssectionmap.vue
  72. 615 151
      src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue
  73. 128 17
      src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue
  74. 0 310
      src/views/User/HmOutFlux/irrigationWater/irrwatermap.vue
  75. 125 40
      src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue
  76. 14 57
      src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue
  77. 14 57
      src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue
  78. 14 6
      src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue
  79. 269 166
      src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue
  80. 269 186
      src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue
  81. 225 77
      src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue
  82. 228 78
      src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue
  83. 203 89
      src/views/User/selectCityAndCounty.vue
  84. 92 78
      src/views/login/loginView.vue
  85. 46 0
      vitest.config.ts.timestamp-1753233310015-7835fe80b1bda.mjs
  86. 46 0
      vitest.config.ts.timestamp-1753239092747-1a7d0d2229938.mjs

+ 3 - 2
.env

@@ -1,2 +1,3 @@
-VITE_API_URL= 'https://www.soilgd.com:5000'
-VITE_TMAP_KEY='2R4BZ-FF4RM-Q6C6U-6TCJL-O2EN5-DVFH5'
+VITE_API_URL= 'https://127.0.0.1:5000'
+VITE_TMAP_KEY='2R4BZ-FF4RM-Q6C6U-6TCJL-O2EN5-DVFH5'
+'https://www.soilgd.com:5000''https://127.0.0.1:5000'

+ 24 - 7
components.d.ts

@@ -7,50 +7,67 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AirsampleChart: typeof import('./src/components/atmpollution/airsampleChart.vue')['default']
+    AirsampleLine: typeof import('./src/components/atmpollution/airsampleLine.vue')['default']
+    AirSampleTencentMap: typeof import('./src/components/atmpollution/airSampleTencentMap.vue')['default']
     AppAside: typeof import('./src/components/layout/AppAside.vue')['default']
     AppAsideForTab2: typeof import('./src/components/layout/AppAsideForTab2.vue')['default']
     AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default']
     AppLayout: typeof import('./src/components/layout/AppLayout.vue')['default']
+    Atmcompanyline: typeof import('./src/components/atmpollution/atmcompanyline.vue')['default']
+    Atmcompanymap: typeof import('./src/components/atmpollution/atmcompanymap.vue')['default']
+    AtmCompanytencentMap: typeof import('./src/components/atmpollution/atmCompanytencentMap.vue')['default']
+    Atmsamplemap: typeof import('./src/components/atmpollution/atmsamplemap.vue')['default']
+    Crosssectionmap: typeof import('./src/components/irrpollution/crosssectionmap.vue')['default']
+    CrossSectionSamplelineData: typeof import('./src/components/irrpollution/crossSectionSamplelineData.vue')['default']
+    CrossSetionData1: typeof import('./src/components/irrpollution/crossSetionData1.vue')['default']
+    CrossSetionData2: typeof import('./src/components/irrpollution/crossSetionData2.vue')['default']
+    CrossSetionTencentmap: typeof import('./src/components/irrpollution/crossSetionTencentmap.vue')['default']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
-    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
-    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
-    ElForm: typeof import('element-plus/es')['ElForm']
-    ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
-    ElOption: typeof import('element-plus/es')['ElOption']
+    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']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
+    HeavyMetalEnterprisechart: typeof import('./src/components/atmpollution/heavyMetalEnterprisechart.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
     IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
     IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
     IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
     IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
+    Irrwatermap: typeof import('./src/components/irrpollution/irrwatermap.vue')['default']
     PaginationComponent: typeof import('./src/components/PaginationComponent.vue')['default']
+    Riverwaterassay: typeof import('./src/components/irrpollution/riverwaterassay.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    TencentMapView: typeof import('./src/components/irrpollution/tencentMapView.vue')['default']
+    Test: typeof import('./src/components/irrpollution/test.vue')['default']
     TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
+    Waterassaydata1: typeof import('./src/components/irrpollution/waterassaydata1.vue')['default']
+    Waterassaydata2: typeof import('./src/components/irrpollution/waterassaydata2.vue')['default']
+    Waterassaydata3: typeof import('./src/components/irrpollution/waterassaydata3.vue')['default']
+    Waterassaydata4: typeof import('./src/components/irrpollution/waterassaydata4.vue')['default']
+    Waterdataline: typeof import('./src/components/irrpollution/waterdataline.vue')['default']
     WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
   }
 }

+ 24 - 609
package-lock.json

@@ -9,9 +9,9 @@
       "version": "0.0.0",
       "dependencies": {
         "@element-plus/icons-vue": "^2.3.1",
-        "@mapbox/polyline": "^1.2.1",
         "@turf/turf": "^7.2.0",
         "@types/d3": "^7.4.3",
+        "@vue-leaflet/vue-leaflet": "^0.10.1",
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^5.1.12",
         "axios": "^1.7.9",
@@ -117,6 +117,7 @@
       "version": "7.26.2",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
       "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+      "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-validator-identifier": "^7.25.9",
@@ -1400,17 +1401,6 @@
       "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
       "license": "MIT"
     },
-    "node_modules/@mapbox/polyline": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmmirror.com/@mapbox/polyline/-/polyline-1.2.1.tgz",
-      "integrity": "sha512-sn0V18O3OzW4RCcPoUIVDWvEGQaBNH9a0y5lgqrf5hUycyw1CzrhEoxV5irzrMNXKCkw1xRsZXcaVbsVZggHXA==",
-      "dependencies": {
-        "meow": "^9.0.0"
-      },
-      "bin": {
-        "polyline": "bin/polyline.bin.js"
-      }
-    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -5083,12 +5073,6 @@
         "@types/lodash": "*"
       }
     },
-    "node_modules/@types/minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
-      "license": "MIT"
-    },
     "node_modules/@types/node": {
       "version": "22.15.31",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz",
@@ -5099,12 +5083,6 @@
         "undici-types": "~6.21.0"
       }
     },
-    "node_modules/@types/normalize-package-data": {
-      "version": "2.4.4",
-      "resolved": "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
-      "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
-      "license": "MIT"
-    },
     "node_modules/@types/tough-cookie": {
       "version": "4.0.5",
       "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
@@ -5313,6 +5291,24 @@
         "vscode-uri": "^3.0.8"
       }
     },
+    "node_modules/@vue-leaflet/vue-leaflet": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmmirror.com/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.10.1.tgz",
+      "integrity": "sha512-RNEDk8TbnwrJl8ujdbKgZRFygLCxd0aBcWLQ05q/pGv4+d0jamE3KXQgQBqGAteE1mbQsk3xoNcqqUgaIGfWVg==",
+      "license": "MIT",
+      "dependencies": {
+        "vue": "^3.2.25"
+      },
+      "peerDependencies": {
+        "@types/leaflet": "^1.5.7",
+        "leaflet": "^1.6.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/leaflet": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@vue/babel-helper-vue-transform-on": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz",
@@ -5912,15 +5908,6 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
-    "node_modules/arrify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/assertion-error": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -6084,32 +6071,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/camelcase": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
-      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/camelcase-keys": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmmirror.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
-      "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
-      "license": "MIT",
-      "dependencies": {
-        "camelcase": "^5.3.1",
-        "map-obj": "^4.0.0",
-        "quick-lru": "^4.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/caniuse-lite": {
       "version": "1.0.30001695",
       "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz",
@@ -6309,7 +6270,7 @@
     },
     "node_modules/coordtransform": {
       "version": "2.1.2",
-      "resolved": "https://registry.npmmirror.com/coordtransform/-/coordtransform-2.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/coordtransform/-/coordtransform-2.1.2.tgz",
       "integrity": "sha512-0xLJApBlrUP+clyLJWIaqg4GXE5JTbAJb5d/CDMqebIksAMMze8eAyO6YfHEIxWJ+c42mXoMHBzWTeUrG7RFhw==",
       "license": "MIT"
     },
@@ -6917,40 +6878,6 @@
         }
       }
     },
-    "node_modules/decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/decamelize-keys": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmmirror.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
-      "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
-      "license": "MIT",
-      "dependencies": {
-        "decamelize": "^1.1.0",
-        "map-obj": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/decamelize-keys/node_modules/map-obj": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/map-obj/-/map-obj-1.0.1.tgz",
-      "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/decimal.js": {
       "version": "10.4.3",
       "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
@@ -7169,15 +7096,6 @@
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
     },
-    "node_modules/error-ex": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz",
-      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "license": "MIT",
-      "dependencies": {
-        "is-arrayish": "^0.2.1"
-      }
-    },
     "node_modules/error-stack-parser-es": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz",
@@ -7465,19 +7383,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/find-up": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
-      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-      "license": "MIT",
-      "dependencies": {
-        "locate-path": "^5.0.0",
-        "path-exists": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/follow-redirects": {
       "version": "1.15.9",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -7577,15 +7482,6 @@
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/gensync": {
       "version": "1.0.0-beta.2",
       "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -7713,27 +7609,6 @@
       "dev": true,
       "license": "ISC"
     },
-    "node_modules/hard-rejection": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmmirror.com/hard-rejection/-/hard-rejection-2.1.0.tgz",
-      "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/hasown": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
-      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "license": "MIT",
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
     "node_modules/he": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -7751,36 +7626,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/hosted-git-info": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
-      "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
-      "license": "ISC",
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/hosted-git-info/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "license": "ISC",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/hosted-git-info/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "license": "ISC"
-    },
     "node_modules/html-encoding-sniffer": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -7905,15 +7750,6 @@
       "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
       "license": "MIT"
     },
-    "node_modules/indent-string": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz",
-      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/ini": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
@@ -7930,12 +7766,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/is-arrayish": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-      "license": "MIT"
-    },
     "node_modules/is-binary-path": {
       "version": "2.1.0",
       "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -7949,21 +7779,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/is-core-module": {
-      "version": "2.16.1",
-      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
-      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
-      "license": "MIT",
-      "dependencies": {
-        "hasown": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/is-docker": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@@ -8197,6 +8012,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/jsdom": {
@@ -8298,15 +8114,6 @@
         "node": ">= 12"
       }
     },
-    "node_modules/kind-of": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz",
-      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/kolorist": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
@@ -8335,12 +8142,6 @@
       "integrity": "sha512-B4UPSn2MT//RkFoyrVjwqQyfKuf4tSmMjJDKQ6nqwCCGgirYKRWHafSH9JmA88WoG5pkuMXBcKQhY32FobxU/g==",
       "license": "MIT"
     },
-    "node_modules/lines-and-columns": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
-      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "license": "MIT"
-    },
     "node_modules/local-pkg": {
       "version": "0.5.1",
       "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz",
@@ -8358,18 +8159,6 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
-    "node_modules/locate-path": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
-      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-      "license": "MIT",
-      "dependencies": {
-        "p-locate": "^4.1.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -8459,18 +8248,6 @@
         "@jridgewell/sourcemap-codec": "^1.5.0"
       }
     },
-    "node_modules/map-obj": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmmirror.com/map-obj/-/map-obj-4.3.0.tgz",
-      "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/marchingsquares": {
       "version": "1.3.3",
       "resolved": "https://registry.npmmirror.com/marchingsquares/-/marchingsquares-1.3.3.tgz",
@@ -8492,32 +8269,6 @@
         "node": ">= 0.10.0"
       }
     },
-    "node_modules/meow": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmmirror.com/meow/-/meow-9.0.0.tgz",
-      "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/minimist": "^1.2.0",
-        "camelcase-keys": "^6.2.2",
-        "decamelize": "^1.2.0",
-        "decamelize-keys": "^1.1.0",
-        "hard-rejection": "^2.1.0",
-        "minimist-options": "4.1.0",
-        "normalize-package-data": "^3.0.0",
-        "read-pkg-up": "^7.0.1",
-        "redent": "^3.0.0",
-        "trim-newlines": "^3.0.0",
-        "type-fest": "^0.18.0",
-        "yargs-parser": "^20.2.3"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
@@ -8591,15 +8342,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/min-indent": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmmirror.com/min-indent/-/min-indent-1.0.1.tgz",
-      "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/minimatch": {
       "version": "9.0.1",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
@@ -8616,29 +8358,6 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/minimist-options": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmmirror.com/minimist-options/-/minimist-options-4.1.0.tgz",
-      "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
-      "license": "MIT",
-      "dependencies": {
-        "arrify": "^1.0.1",
-        "is-plain-obj": "^1.1.0",
-        "kind-of": "^6.0.3"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/minimist-options/node_modules/is-plain-obj": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
-      "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/minipass": {
       "version": "7.1.2",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -8760,21 +8479,6 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
-    "node_modules/normalize-package-data": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
-      "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "hosted-git-info": "^4.0.1",
-        "is-core-module": "^2.5.0",
-        "semver": "^7.3.4",
-        "validate-npm-package-license": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -8910,42 +8614,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-      "license": "MIT",
-      "dependencies": {
-        "p-try": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/p-locate": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
-      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-      "license": "MIT",
-      "dependencies": {
-        "p-limit": "^2.2.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/p-try": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
-      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/package-json-from-dist": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -8960,30 +8628,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/parse-json": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz",
-      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.0.0",
-        "error-ex": "^1.3.1",
-        "json-parse-even-better-errors": "^2.3.0",
-        "lines-and-columns": "^1.1.6"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/parse-json/node_modules/json-parse-even-better-errors": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
-      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "license": "MIT"
-    },
     "node_modules/parse-ms": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
@@ -9017,15 +8661,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/path-exists": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
-      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/path-key": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -9036,12 +8671,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/path-parse": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "license": "MIT"
-    },
     "node_modules/path-scurry": {
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
@@ -9316,15 +8945,6 @@
       ],
       "license": "MIT"
     },
-    "node_modules/quick-lru": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-4.0.1.tgz",
-      "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/quickselect": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/quickselect/-/quickselect-2.0.0.tgz",
@@ -9354,83 +8974,6 @@
         "node": "^18.17.0 || >=20.5.0"
       }
     },
-    "node_modules/read-pkg": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz",
-      "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/normalize-package-data": "^2.4.0",
-        "normalize-package-data": "^2.5.0",
-        "parse-json": "^5.0.0",
-        "type-fest": "^0.6.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/read-pkg-up": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmmirror.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
-      "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
-      "license": "MIT",
-      "dependencies": {
-        "find-up": "^4.1.0",
-        "read-pkg": "^5.2.0",
-        "type-fest": "^0.8.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/read-pkg-up/node_modules/type-fest": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz",
-      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
-      "license": "(MIT OR CC0-1.0)",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/read-pkg/node_modules/hosted-git-info": {
-      "version": "2.8.9",
-      "resolved": "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
-      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
-      "license": "ISC"
-    },
-    "node_modules/read-pkg/node_modules/normalize-package-data": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
-      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "hosted-git-info": "^2.1.4",
-        "resolve": "^1.10.0",
-        "semver": "2 || 3 || 4 || 5",
-        "validate-npm-package-license": "^3.0.1"
-      }
-    },
-    "node_modules/read-pkg/node_modules/semver": {
-      "version": "5.7.2",
-      "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz",
-      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver"
-      }
-    },
-    "node_modules/read-pkg/node_modules/type-fest": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz",
-      "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
-      "license": "(MIT OR CC0-1.0)",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
@@ -9457,45 +9000,12 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
-    "node_modules/redent": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmmirror.com/redent/-/redent-3.0.0.tgz",
-      "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
-      "license": "MIT",
-      "dependencies": {
-        "indent-string": "^4.0.0",
-        "strip-indent": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/regenerator-runtime": {
       "version": "0.14.1",
       "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
       "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
       "license": "MIT"
     },
-    "node_modules/resolve": {
-      "version": "1.22.10",
-      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
-      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
-      "license": "MIT",
-      "dependencies": {
-        "is-core-module": "^2.16.0",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
@@ -9697,6 +9207,7 @@
       "version": "7.6.3",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
       "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "dev": true,
       "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
@@ -9823,38 +9334,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/spdx-correct": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz",
-      "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "spdx-expression-parse": "^3.0.0",
-        "spdx-license-ids": "^3.0.0"
-      }
-    },
-    "node_modules/spdx-exceptions": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
-      "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
-      "license": "CC-BY-3.0"
-    },
-    "node_modules/spdx-expression-parse": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
-      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
-      "license": "MIT",
-      "dependencies": {
-        "spdx-exceptions": "^2.1.0",
-        "spdx-license-ids": "^3.0.0"
-      }
-    },
-    "node_modules/spdx-license-ids": {
-      "version": "3.0.21",
-      "resolved": "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
-      "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
-      "license": "CC0-1.0"
-    },
     "node_modules/speakingurl": {
       "version": "14.0.1",
       "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
@@ -10020,18 +9499,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/strip-indent": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmmirror.com/strip-indent/-/strip-indent-3.0.0.tgz",
-      "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
-      "license": "MIT",
-      "dependencies": {
-        "min-indent": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/strip-literal": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz",
@@ -10065,18 +9532,6 @@
         "node": ">=16"
       }
     },
-    "node_modules/supports-preserve-symlinks-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
-      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/svg-tags": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
@@ -10288,15 +9743,6 @@
         "node": ">=18"
       }
     },
-    "node_modules/trim-newlines": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmmirror.com/trim-newlines/-/trim-newlines-3.0.1.tgz",
-      "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/tslib": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
@@ -10309,23 +9755,11 @@
       "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
       "license": "ISC"
     },
-    "node_modules/type-fest": {
-      "version": "0.18.1",
-      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.18.1.tgz",
-      "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
-      "license": "(MIT OR CC0-1.0)",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/typescript": {
       "version": "5.6.3",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
       "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
-      "devOptional": true,
+      "dev": true,
       "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
@@ -10632,16 +10066,6 @@
         "base64-arraybuffer": "^1.0.2"
       }
     },
-    "node_modules/validate-npm-package-license": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
-      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
-      "license": "Apache-2.0",
-      "dependencies": {
-        "spdx-correct": "^3.0.0",
-        "spdx-expression-parse": "^3.0.0"
-      }
-    },
     "node_modules/vite": {
       "version": "6.3.5",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz",
@@ -12370,15 +11794,6 @@
       "dev": true,
       "license": "ISC"
     },
-    "node_modules/yargs-parser": {
-      "version": "20.2.9",
-      "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz",
-      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/yoctocolors": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",

+ 1 - 1
package.json

@@ -13,9 +13,9 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
-    "@mapbox/polyline": "^1.2.1",
     "@turf/turf": "^7.2.0",
     "@types/d3": "^7.4.3",
+    "@vue-leaflet/vue-leaflet": "^0.10.1",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.12",
     "axios": "^1.7.9",

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 5 - 0
public/data/韶关市乡镇划分图.geojson


BIN
public/images/仁化县.png


BIN
public/农业化肥采集.png


BIN
public/农业投入品使用情况.png


BIN
public/农业投入品样品采集.png


BIN
public/农业投入品用量计算方法.png


BIN
public/农业投入品重金属输入通量计算方法.png


BIN
public/农业投入品镉含量.png


BIN
public/农田土壤重金属主要的输入输出途径.png


BIN
public/农膜采集.png


BIN
public/农药采集.png


BIN
public/各农业投入品测试结果.png


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


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


BIN
public/有机肥采集.png


+ 0 - 1
src/App.vue

@@ -1,7 +1,6 @@
 <script setup lang='ts'>
 import { RouterView } from "vue-router"
 import request from './utils/request';
-import Map from "./views/User/HmOutFlux/irrigationWater/map.vue";
 request({
   url: '/table',
   method: 'get'

BIN
src/assets/bg/atmospheric_deposition.png


BIN
src/assets/login-bg.png


BIN
src/assets/right-bg.png


BIN
src/assets/samplingequipment1.png


BIN
src/assets/samplingequipment2.png


BIN
src/assets/samplingequipment3.png


BIN
src/assets/samplingsite1.png


BIN
src/assets/samplingsite2.png


BIN
src/assets/samplingsite3.png


+ 0 - 0
src/views/User/HmOutFlux/atmosDeposition/airSampleTencentMap.vue → src/components/atmpollution/airSampleTencentMap.vue


+ 487 - 0
src/components/atmpollution/airsampleChart.vue

@@ -0,0 +1,487 @@
+<template>
+  <div class="atmosphere-summary">
+    <!-- 图表容器 -->
+    <div ref="chartRef" class="chart-box"></div>
+    
+    <!-- 状态提示 -->
+    <div v-if="loading" class="status">
+      <div class="spinner"></div>
+      <p>数据加载中...</p>
+    </div>
+    
+    <div v-else-if="error" class="status error">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+      <div v-if="errorDetails" class="error-details">
+        <p>错误详情:</p>
+        <pre>{{ errorDetails }}</pre>
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
+import * as echarts from 'echarts'
+import axios from 'axios'
+
+// 接收计算方式(重量/体积)
+const props = defineProps({
+  calculationMethod: {
+    type: String,
+    required: true,
+    default: 'weight'
+  }
+})
+
+// --------------------------
+// 配置区
+// --------------------------
+const BACKEND_PORT = '8000';
+const API_URL = `http://localhost:${BACKEND_PORT}/api/vector/export/all?table_name=Atmo_sample_data`;
+
+// 重量指标字段
+const WEIGHT_FIELDS = [
+  'Cr_particulate',
+  'As_particulate',
+  'Cd_particulate',
+  'Hg_particulate',
+  'Pb_particulate'
+];
+
+// 体积字段名
+const VOLUME_FIELD = 'volume'; 
+
+// 自定义颜色(用户指定)
+const COLORS = ['#ff4d4f99', '#1890ff', '#ffd700', '#52c41a88', '#722ed199'];
+// --------------------------
+
+// 响应式数据
+const chartRef = ref(null);
+const loading = ref(true);
+const error = ref('');
+const showLog = ref(false); // 默认隐藏日志
+const fullLog = ref('');
+let myChart = null;
+
+// 记录日志
+const log = (message) => {
+  const time = new Date().toLocaleTimeString();
+  fullLog.value += `[${time}] ${message}\n`;
+  console.log(`[日志] ${message}`);
+};
+
+/**
+ * 修复JSON中的非法值
+ */
+const fixInvalidJsonValues = (rawData) => {
+  if (typeof rawData !== 'string') {
+    rawData = JSON.stringify(rawData);
+  }
+  
+  const fixedData = rawData
+    .replace(/:\s*NaN\b/g, ': null')
+    .replace(/:\s*"N"\b/g, ': null')
+    .replace(/:\s*"NaN"\b/g, ': null')
+    .replace(/:\s*Infinity\b/g, ': null')
+    .replace(/:\s*-\s*Infinity\b/g, ': null')
+    .replace(/:\s+/g, ': ')
+    .replace(/,\s+/g, ', ');
+  
+  return fixedData;
+};
+
+/**
+ * 重量转体积计算
+ */
+function weightToVolume(weight, volume) {
+  if (weight === undefined || weight === null) {
+    log(`重量值无效: ${weight}`);
+    return 0;
+  }
+  if (volume === undefined || volume === null || volume === 0 || isNaN(volume)) {
+    log(`体积值无效: ${volume}(已自动替换为1)`);
+    volume = 1;
+  }
+  
+  const weightNum = parseFloat(weight);
+  const volumeNum = parseFloat(volume);
+  
+  if (isNaN(weightNum)) {
+    log(`重量无法转换为数字: ${weight}`);
+    return 0;
+  }
+  
+  const ug = weightNum * 1000;
+  return parseFloat((ug / volumeNum).toFixed(2));
+}
+
+/**
+ * 提取区县
+ */
+function getRegion(location) {
+  if (!location || typeof location !== 'string') {
+    return '未知区县';
+  }
+
+  const regions = [
+    '浈江区', '武江区', '曲江区', '乐昌市', 
+    '南雄市', '始兴县', '仁化县', '翁源县', 
+    '新丰县', '乳源瑶族自治县'
+  ];
+
+  // 精确匹配
+  for (const region of regions) {
+    if (location.includes(region)) {
+      return region;
+    }
+  }
+
+  // 模糊匹配
+  const aliasMap = {
+    '浈江': '浈江区', '武江': '武江区', '曲江': '曲江区',
+    '乐昌': '乐昌市', '南雄': '南雄市', '始兴': '始兴县',
+    '仁化': '仁化县', '翁源': '翁源县', '新丰': '新丰县',
+    '乳源': '乳源瑶族自治县'
+  };
+  
+  for (const [alias, region] of Object.entries(aliasMap)) {
+    if (location.includes(alias)) {
+      return region;
+    }
+  }
+
+  return '未知区县';
+}
+
+/**
+ * 数据处理
+ */
+async function processData() {
+  try {
+    log('开始数据处理');
+    const response = await axios.get(API_URL, { 
+      timeout: 15000,
+      responseType: 'text'
+    });
+    
+    const fixedJson = fixInvalidJsonValues(response.data);
+    const geoData = JSON.parse(fixedJson);
+
+    if (!geoData || !geoData.features || !Array.isArray(geoData.features)) {
+      throw new Error('数据结构错误,缺少features数组');
+    }
+    log(`解析到${geoData.features.length}条数据`);
+
+    // 处理数据
+    const processedItems = geoData.features.map((feature, index) => {
+      const props = feature.properties || {};
+      return {
+        id: index,
+        location: props.sampling_location || '',
+        region: getRegion(props.sampling_location || ''),
+        volume: props[VOLUME_FIELD],
+        weights: WEIGHT_FIELDS.reduce((acc, field) => {
+          acc[field] = props[field];
+          return acc;
+        }, {})
+      };
+    });
+
+    // 统计数据
+    const regionStats = {};
+    const totalStats = {};
+    WEIGHT_FIELDS.forEach(field => {
+      totalStats[field] = { sum: 0, count: 0 };
+      totalStats[`${field}_volume`] = { sum: 0, count: 0 };
+    });
+
+    processedItems.forEach(item => {
+      const { region, volume, weights } = item;
+      if (!regionStats[region]) {
+        regionStats[region] = {};
+        WEIGHT_FIELDS.forEach(field => {
+          regionStats[region][field] = { sum: 0, count: 0 };
+          regionStats[region][`${field}_volume`] = { sum: 0, count: 0 };
+        });
+      }
+
+      WEIGHT_FIELDS.forEach(field => {
+        const weightValue = weights[field];
+        const weightNum = parseFloat(weightValue);
+
+        if (!isNaN(weightNum)) {
+          regionStats[region][field].sum += weightNum;
+          regionStats[region][field].count += 1;
+          totalStats[field].sum += weightNum;
+          totalStats[field].count += 1;
+        }
+
+        const volumeValue = weightToVolume(weightValue, volume);
+        regionStats[region][`${field}_volume`].sum += volumeValue;
+        regionStats[region][`${field}_volume`].count += 1;
+        totalStats[`${field}_volume`].sum += volumeValue;
+        totalStats[`${field}_volume`].count += 1;
+      });
+    });
+
+    // 准备图表数据
+    const chartRegions = Object.keys(regionStats).filter(r => r !== '未知区县');
+    if (chartRegions.length === 0) chartRegions.push('未知区县');
+    chartRegions.push('全市平均');
+
+    // 生成系列数据
+    const series = WEIGHT_FIELDS.map((field, index) => {
+      const metricType = props.calculationMethod === 'volume' 
+        ? `${field}_volume` 
+        : field;
+      
+      const data = chartRegions.map(region => {
+        if (region === '全市平均') {
+          return totalStats[metricType].count > 0 
+            ? (totalStats[metricType].sum / totalStats[metricType].count).toFixed(2)
+            : '0.00';
+        }
+        
+        const stats = regionStats[region][metricType];
+        return stats.count > 0 
+          ? (stats.sum / stats.count).toFixed(2)
+          : '0.00';
+      });
+
+      return {
+        name: field.replace('_particulate', ''), // 图例名称(不带后缀)
+        type: 'bar',
+        data,
+        itemStyle: { 
+          color: COLORS[index % COLORS.length] // 使用用户指定的颜色
+        },
+        label: {
+          show: true,
+          position: 'top',
+          fontSize: 12
+        }
+      };
+    });
+
+    return { regions: chartRegions, series };
+
+  } catch (err) {
+    error.value = '数据处理失败';
+    return null;
+  }
+}
+
+/**
+ * 初始化图表(带单位显示)
+ */
+async function initChart() {
+  loading.value = true;
+  error.value = '';
+  
+  try {
+    await nextTick();
+    if (!chartRef.value) {
+      throw new Error('图表容器未挂载');
+    }
+
+    const chartData = await processData();
+    if (!chartData) return;
+
+    // 确定单位(核心修改:添加单位逻辑)
+    const { unit, titleText } = props.calculationMethod === 'weight' 
+      ? { 
+          unit: 'mg/kg', 
+          titleText: '各区域空气颗粒物重量指标平均值' ,
+        } 
+      : { 
+          unit: 'ug/m³',  // 假设体积单位为ug/m³,可根据实际需求修改
+          titleText: '各区域空气颗粒物体积指标平均值' ,
+        };
+
+    // 销毁旧图表
+    if (myChart) myChart.dispose();
+    myChart = echarts.init(chartRef.value);
+
+    // 设置图表配置(带单位显示)
+    myChart.setOption({
+      title: { 
+        text: titleText,
+        subtext: `单位: ${unit}`, // 标题显示单位
+        left: 'center',
+        textStyle:{fontSize:20},
+        subtextStyle:{fontSize:18}
+      },
+      tooltip: { //提示框
+        trigger: 'axis',
+        formatter: function(params) {
+          // Tooltip显示单位
+          let res = `${params[0].name}<br/>`;
+          params.forEach(item => {
+            res += `${item.marker} ${item.seriesName}: ${item.value} ${unit}<br/>`;
+          });
+          return res;
+        },
+        textStyle:{fontSize:15}
+      },
+      xAxis: {
+        type: 'category',
+        data: chartData.regions,
+        axisLabel: { rotate: 45 ,fontSize:18 }
+      },
+      yAxis: { 
+        type: 'value',
+        axisLabel: {
+          formatter: `{value} ${unit}` // Y轴显示单位
+          ,fontSize:15
+        }
+      },
+      series: chartData.series.map(series => ({  // 遍历每个系列,添加 label 配置
+       ...series,  // 保留原有配置
+       label: {
+         show: true,  // 显示数值标签
+         position: 'top',  // 标签位置(顶部)
+         fontSize: 15,  // 这里才是柱状图数值的字体大小!
+         color: '#333'  // 可选:设置文字颜色
+       }
+      })),
+      legend: { //图例
+        data: chartData.series.map(s => s.name),
+        bottom: 10,
+        textStyle:{fontSize:18}
+      },
+      grid: {
+        left: '5%', right: '5%', bottom: '20%', top: '15%',
+        containLabel: true,
+        axisLabel:{fontSize:18}
+      }
+    }, true);
+
+    // 监听窗口大小
+    const handleResize = () => myChart.resize();
+    window.addEventListener('resize', handleResize);
+    onUnmounted(() => window.removeEventListener('resize', handleResize));
+
+  } catch (err) {
+    error.value = '图表加载失败';
+  } finally {
+    loading.value = false;
+  }
+}
+
+// 监听计算方式变化
+watch(() => props.calculationMethod, initChart);
+
+// 组件挂载后初始化
+onMounted(() => {
+  initChart();
+});
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (myChart) myChart.dispose();
+});
+</script>
+
+<style scoped>
+.atmosphere-summary {
+  width: 100%;
+  max-width: 1400px;
+  margin: 0 auto;
+  padding: 20px;
+  box-sizing: border-box;
+  position: relative;
+}
+
+.chart-box {
+  width: 100%;
+  height: 600px;
+  min-height: 400px;
+  background: #fff;
+  border: 1px solid #e9ecef;
+  border-radius: 8px;
+}
+
+.status {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  padding: 20px;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 6px;
+  text-align: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  max-width: 80%;
+}
+
+.error {
+  color: #dc3545;
+  border: 1px solid #f5c6cb;
+  background: #f8d7da;
+}
+
+.error-details {
+  margin-top: 15px;
+  text-align: left;
+  font-size: 14px;
+}
+
+.error-details pre {
+  background: rgba(255, 255, 255, 0.8);
+  padding: 10px;
+  border-radius: 4px;
+  overflow: auto;
+  max-height: 200px;
+  white-space: pre-wrap;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  margin: 0 auto 15px;
+  border: 4px solid #e9ecef;
+  border-top: 4px solid #007bff;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.debug-panel {
+  margin-top: 20px;
+  padding: 15px;
+  background: #f8f9fa;
+  border-radius: 6px;
+  font-size: 14px;
+}
+
+.log-toggle {
+  background: #007bff;
+  color: white;
+  border: none;
+  padding: 6px 12px;
+  border-radius: 4px;
+  cursor: pointer;
+  margin-bottom: 10px;
+}
+
+.log-content {
+  max-height: 300px;
+  overflow: auto;
+  background: #fff;
+  padding: 10px;
+  border-radius: 4px;
+  border: 1px solid #e9ecef;
+}
+
+.log-content pre {
+  margin: 0;
+  white-space: pre-wrap;
+  font-family: monospace;
+  font-size: 12px;
+}
+</style>

+ 456 - 0
src/components/atmpollution/airsampleLine.vue

@@ -0,0 +1,456 @@
+<template>
+  <div class="container mx-auto px-4 py-8">
+    <!-- 错误提示(带原始响应预览) -->
+    <div v-if="error" class="status error mb-4">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+      <div class="raw-response" v-if="rawResponse">
+        <button @click="showRaw = !showRaw" class="mt-2">
+          {{ showRaw ? '收起原始响应' : '查看原始响应(前1000字符)' }}
+        </button>
+        <pre v-if="showRaw" class="mt-2 bg-gray-50 p-2 text-sm">{{ truncatedRawResponse }}</pre>
+      </div>
+    </div>
+
+    <div class="bg-white rounded-xl shadow-lg overflow-hidden">
+      <!-- 加载状态 -->
+      <div v-if="loading" class="py-20 flex justify-center items-center">
+        <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
+      </div>
+      
+      <!-- 数据展示区:表格 + 地图 + 柱状图 -->
+      <div v-else-if="filteredData.length > 0" class="flex flex-col md:flex-row">
+        <!-- 表格 -->
+        <div class="w-full md:w-1/2 overflow-x-auto">
+          <table class="min-w-full divide-y divide-gray-200">
+            <thead class="bg-gray-50">
+              <tr>
+                <th 
+                  v-for="(col, index) in displayColumns" 
+                  :key="index"
+                  :style="{ width: col.width }"  
+                  class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors"
+                  @click="sortData(col.label)"
+                >
+                  <div class="flex items-center justify-between">
+                    {{ col.label }}
+                    <span v-if="sortKey === col.label" class="ml-1 text-gray-400">
+                      {{ sortOrder === 'asc' ? '↑' : '↓' }}
+                    </span>
+                  </div>
+                </th>
+              </tr>
+            </thead>
+            <tbody class="bg-white divide-y divide-gray-200">
+              <tr v-for="(item, rowIndex) in sortedData" :key="rowIndex" 
+                  class="hover:bg-gray-50 transition-colors duration-150">
+                <td 
+                  v-for="(col, colIndex) in displayColumns" 
+                  :key="colIndex"
+                  :style="{ width: col.width }"  
+                  class="px-6 py-4 whitespace-nowrap text-sm"
+                >
+                  <div class="flex items-center">
+                    <div class="text-gray-900 font-medium">
+                      {{ formatValue(item, col) }}
+                    </div>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <!-- 地图 + 柱状图(右侧区域) -->
+        <div class="w-full md:w-1/2 p-4 flex flex-col">
+          <!-- 地图(依赖:@vue-leaflet/vue-leaflet) -->
+          <div class="h-64 mb-4">
+            <LMap 
+              :center="mapCenter" 
+              :zoom="12" 
+              style="width: 100%; height: 100%"
+            >
+              <LTileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
+              <LMarker 
+                v-for="(item, idx) in filteredData" 
+                :key="idx" 
+                :lat-lng="[item.latitude, item.longitude]"
+              >
+                <LPopup>{{ item.sampling_location }}</LPopup>
+              </LMarker>
+            </LMap>
+          </div>
+
+          <!-- 柱状图(依赖:echarts) -->
+          <div class="h-64">
+            <div ref="chart" class="w-full h-full"></div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 空数据状态 -->
+      <div v-else class="p-8 text-center">
+        <div class="flex flex-col items-center justify-center">
+          <div class="text-gray-400 mb-4">
+            <i class="fa fa-database text-5xl"></i>
+          </div>
+          <h3 class="text-lg font-medium text-gray-900 mb-1">暂无有效数据</h3>
+          <p class="text-gray-500">已过滤全空行</p>
+        </div>
+      </div>
+  
+      <!-- 数据统计 -->
+      <div class="p-4 bg-gray-50 border-t border-gray-200">
+        <div class="flex flex-col md:flex-row justify-between items-center">
+          <div class="text-sm text-gray-500 mb-2 md:mb-0">
+            共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
+import { LMap, LTileLayer, LMarker, LPopup } from '@vue-leaflet/vue-leaflet'; // 地图组件
+import * as echarts from 'echarts'; // 柱状图
+
+// ========== 接口配置 ==========
+const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data'; 
+
+// ========== 响应式数据 ==========
+const props = defineProps({
+  calculationMethod: {
+    type: String,
+    required: true,
+    default: 'weight'
+  }
+});
+
+const waterData = ref([]);
+const loading = ref(true);
+const error = ref('');
+const rawResponse = ref('');  
+const showRaw = ref(false);   
+const sortKey = ref('');      
+const sortOrder = ref('asc'); 
+const mapCenter = ref([23.5, 116.5]); // 地图初始中心(根据实际数据调整)
+const chartInstance = ref(null); // ECharts实例
+
+// 换算函数(重量→体积)
+function calculateConcentration(heavyMetalWeight, volume) {
+  if (heavyMetalWeight === undefined || volume === undefined || isNaN(heavyMetalWeight) || isNaN(volume) || volume === 0) {
+    return '未知';
+  }
+  const ug = heavyMetalWeight * 1000; 
+  const concentration = ug / volume;
+  return concentration.toFixed(2); // 保留2位小数
+}
+
+function calculateParticleConcentration(particleWeight, volume) {
+  if (particleWeight === undefined || volume === undefined || isNaN(particleWeight) || isNaN(volume) || volume === 0) {
+    return '未知';
+  }
+  const ug = particleWeight * 1000; 
+  const concentration = ug / volume;
+  return concentration.toFixed(2); // 保留2位小数
+}
+
+// 列配置(补充 width,根据内容合理分配宽度)
+const commonColumns = [
+  { key: 'sampling_location', label: '采样位置', type: 'string', width: '180px' },
+  { key: 'sample_name', label: '样品名称', type: 'string', width: '70px' },
+  { key: 'latitude', label: '纬度', type: 'number', width: '60px' }, // 地图依赖字段
+  { key: 'longitude', label: '经度', type: 'number', width: '60px' },// 地图依赖字段
+];
+
+const weightColumns = [
+  { key: 'Cr_particulate', label: 'Cr mg/kg', type: 'number', width: '80px' },
+  { key: 'As_particulate', label: 'As mg/kg', type: 'number', width: '80px' },
+  { key: 'Cd_particulate', label: 'Cd mg/kg', type: 'number', width: '80px' },
+  { key: 'Hg_particulate', label: 'Hg mg/kg', type: 'number', width: '80px' },
+  { key: 'Pb_particulate', label: 'Pb mg/kg', type: 'number', width: '80px' },
+  { key: 'particle_weight', label: '颗粒物重量 mg', type: 'number', width: '140px' },
+];
+
+const volumeColumns = [
+  { key: 'standard_volume', label: '标准体积 m³', type: 'number', width: '120px' },
+  { label: 'Cr ug/m³', getValue: (item) => calculateConcentration(item.Cr_particulate, item.standard_volume), type: 'number', width: '80px' },
+  { label: 'As ug/m³', getValue: (item) => calculateConcentration(item.As_particulate, item.standard_volume), type: 'number', width: '80px' },
+  { label: 'Cd ug/m³', getValue: (item) => calculateConcentration(item.Cd_particulate, item.standard_volume), type: 'number', width: '80px' },
+  { label: 'Hg ug/m³', getValue: (item) => calculateConcentration(item.Hg_particulate, item.standard_volume), type: 'number', width: '80px' },
+  { label: 'Pb ug/m³', getValue: (item) => calculateConcentration(item.Pb_particulate, item.standard_volume), type: 'number', width: '80px' },
+  { label: '颗粒物浓度 ug/m³', getValue: (item) => calculateParticleConcentration(item.particle_weight, item.standard_volume), type: 'number', width: '140px' },
+];
+
+// 动态生成显示列
+const displayColumns = computed(() => {
+  return props.calculationMethod === 'volume' 
+    ? [...commonColumns, ...volumeColumns] 
+    : [...commonColumns, ...weightColumns];
+});
+
+// 数值格式化
+const formatValue = (item, col) => {
+  if (col.getValue) {
+    const val = col.getValue(item);
+    return val === '未知' ? '-' : val;
+  } else {
+    const value = item[col.key];
+    if (value === null || value === undefined || value === '') return '-';
+    if (col.type === 'number') {
+      const num = parseFloat(value);
+      return isNaN(num) ? '-' : num.toFixed(2); // 统一保留2位小数
+    } else {
+      return value;
+    }
+  }
+};
+
+// 过滤全空行(允许0值)
+const filteredData = computed(() => {
+  return waterData.value.filter(item => {
+    return displayColumns.value.some(col => {
+      let val = col.getValue ? col.getValue(item) : item[col.key];
+      if (col.type === 'string') {
+        return val !== null && val !== '' && val !== '-';
+      } else {
+        const num = parseFloat(val);
+        return !isNaN(num); // 允许0值,仅排除非数字
+      }
+    });
+  });
+});
+
+// 排序功能
+const sortedData = computed(() => {
+  if (!sortKey.value) return filteredData.value;
+  
+  const sortCol = displayColumns.value.find(col => col.label === sortKey.value);
+  if (!sortCol) return filteredData.value;
+  
+  return [...filteredData.value].sort((a, b) => {
+    let valA = sortCol.getValue ? sortCol.getValue(a) : a[sortCol.key];
+    let valB = sortCol.getValue ? sortCol.getValue(b) : b[sortCol.key];
+    
+    if (sortCol.type === 'string') {
+      const strA = valA.toString().trim();
+      const strB = valB.toString().trim();
+      return sortOrder.value === 'asc' 
+        ? strA.localeCompare(strB) 
+        : strB.localeCompare(strA);
+    }
+    
+    const numA = parseFloat(valA) || -Infinity;
+    const numB = parseFloat(valB) || -Infinity;
+    if (numA < numB) return sortOrder.value === 'asc' ? -1 : 1;
+    if (numA > numB) return sortOrder.value === 'asc' ? 1 : -1;
+    return 0;
+  });
+});
+
+// 切换排序
+const sortData = (label) => {
+  const targetCol = displayColumns.value.find(col => col.label === label);
+  if (!targetCol) return;
+  
+  if (sortKey.value === label) {
+    sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
+  } else {
+    sortKey.value = label;
+    sortOrder.value = 'asc';
+  }
+};
+
+// 截断原始响应(前1000字符)
+const truncatedRawResponse = computed(() => {
+  return rawResponse.value.length > 1000 
+    ? rawResponse.value.slice(0, 1000) + '...' 
+    : rawResponse.value;
+});
+
+// 柱状图数据(动态生成)
+const chartData = computed(() => {
+  const xData = filteredData.value.map(item => item.sample_name);
+  const yData = filteredData.value.map(item => {
+    if (props.calculationMethod === 'volume') {
+      const val = item['Cr ug/m³'];
+      return val !== '-' ? parseFloat(val) : 0;
+    } else {
+      const val = item.Cr_particulate;
+      return val !== '-' ? parseFloat(val) : 0;
+    }
+  });
+
+  return {
+    xAxis: { type: 'category', data: xData },
+    yAxis: { type: 'value' },
+    series: [{ name: 'Cr 浓度', type: 'bar', data: yData }],
+  };
+});
+
+// 初始化柱状图
+const initChart = () => {
+  nextTick(() => {
+    if (chartInstance.value) chartInstance.value.dispose();
+    chartInstance.value = echarts.init($refs.chart);
+    chartInstance.value.setOption(chartData.value);
+  });
+};
+
+// 监听数据变化,更新图表
+watch(filteredData, () => {
+  if (chartInstance.value) {
+    chartInstance.value.setOption(chartData.value);
+  }
+});
+
+// 数据请求 & 修复逻辑(含超时控制)
+const fetchData = async () => {
+  try {
+    loading.value = true;
+    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;
+
+    // 修复 JSON 语法(替换 NaN/Infinity)
+    rawText = rawText
+      .replace(/:\s*NaN/g, ': null')
+      .replace(/:\s*Infinity/g, ': null');
+
+    let data;
+    try {
+      data = JSON.parse(rawText);
+    } catch (parseErr) {
+      error.value = `数据解析失败:${parseErr.message}\n原始响应:${rawText.slice(0, 200)}...`;
+      console.error(parseErr, rawText);
+      loading.value = false;
+      return;
+    }
+
+    // 兼容接口格式
+    let features = [];
+    if (data.type === 'FeatureCollection' && Array.isArray(data.features)) {
+      features = data.features;
+    } else if (Array.isArray(data)) {
+      features = data;
+    } else {
+      throw new Error('接口格式异常,需为 FeatureCollection 或数组');
+    }
+
+    // 提取数据(含经纬度)
+    waterData.value = features.map(feature => 
+      feature.properties ? feature.properties : feature
+    );
+
+    // 更新地图中心(取第一个点的经纬度,无数据则保持默认)
+    if (waterData.value.length > 0) {
+      mapCenter.value = [
+        waterData.value[0].latitude, 
+        waterData.value[0].longitude
+      ];
+    }
+
+    console.log('✅ 数据加载完成,记录数:', waterData.value.length);
+    initChart(); // 初始化图表
+
+  } catch (err) {
+    if (err.name === 'AbortError') {
+      error.value = '请求超时!请检查网络或接口响应速度';
+    } else {
+      error.value = `数据加载失败:${err.message}`;
+    }
+    console.error(err);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 组件挂载时加载数据
+onMounted(() => {
+  fetchData();
+});
+
+// 卸载时销毁图表
+onUnmounted(() => {
+  if (chartInstance.value) {
+    chartInstance.value.dispose();
+  }
+});
+</script>
+
+<style scoped>
+/* 错误提示样式 */
+.status.error {
+  color: #dc2626;
+  background: #fee2e2;
+  padding: 12px 16px;
+  border-radius: 6px;
+}
+.status.error button {
+  cursor: pointer;
+  background: #ff4d4f;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  padding: 4px 8px;
+}
+.raw-response pre {
+  white-space: pre-wrap;
+  word-break: break-all;
+  background: #f9fafb;
+  padding: 8px;
+  border-radius: 4px;
+  font-size: 12px;
+}
+
+/* 表格核心样式:固定布局 + 列宽生效 */
+table {
+  border-collapse: collapse;
+  table-layout: fixed; 
+  width: 100%;         
+}
+
+th, td {
+  border: 1px solid #d1d5db;
+  text-align: center;
+  padding: 10px 6px;   
+  min-width: 60px;     
+  white-space: normal; 
+  overflow: hidden;    
+  text-overflow: ellipsis; 
+}
+
+/* 地图 & 图表容器 */
+.leaflet-container {
+  width: 100%;
+  height: 100%;
+}
+.echarts-container {
+  width: 100%;
+  height: 100%;
+}
+
+/* 响应式优化 */
+@media (max-width: 768px) {
+  th, td {
+    padding: 8px 4px;
+    font-size: 14px;
+  }
+  .flex-col md:flex-row {
+    flex-direction: column;
+  }
+}
+</style>

+ 0 - 0
src/views/User/HmOutFlux/atmosDeposition/atmCompanytencentMap.vue → src/components/atmpollution/atmCompanytencentMap.vue


+ 219 - 0
src/components/atmpollution/atmcompanyline.vue

@@ -0,0 +1,219 @@
+<template>
+  <div class="region-average-chart">
+    <!-- 错误提示 -->
+    <div v-if="error" class="status error">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+      <div class="raw-response" v-if="rawResponse">
+        <button @click="showRaw = !showRaw">
+          {{ showRaw ? '收起原始响应' : '查看原始响应(前1000字符)' }}
+        </button>
+        <pre v-if="showRaw">{{ truncatedRawResponse }}</pre>
+      </div>
+    </div>
+
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-state">
+      <div class="spinner"></div>
+      <p>数据加载中...</p>
+    </div>
+
+    <!-- 数据表格 -->
+    <div v-else class="table-container">
+      <table class="data-table">
+        <thead>
+          <tr>
+            <th>污染源序号</th>
+            <th>公司名称</th>
+            <th>公司类型</th>
+            <th>所属区县</th>
+            <th>颗粒物排放(t/a)</th>
+            <th>经度</th>
+            <th>纬度</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="item in tableData" :key="item.id">
+            <td>{{ item.id }}</td>
+            <td>{{ item.company_name }}</td>
+            <td>{{ item.company_type }}</td>
+            <td>{{ item.county }}</td>
+            <td>{{ item.particulate_emission }}</td>
+            <td>{{ item.longitude.toFixed(6) }}</td>
+            <td>{{ item.latitude.toFixed(6) }}</td>
+          </tr>
+          <tr v-if="tableData.length === 0">
+            <td colspan="7" class="empty-state">暂无有效数据</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue';
+import { wgs84togcj02 } from 'coordtransform'; // 经纬度转换(按需保留)
+
+// ========== 接口配置 ==========
+const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=atmo_company'; 
+
+// ========== 响应式数据 ==========
+const error = ref('');        // 错误信息
+const loading = ref(true);    // 加载状态
+const tableData = ref([]);    // 表格数据
+const rawResponse = ref('');  // 原始响应文本(调试用)
+const showRaw = ref(false);   // 是否展开原始响应
+
+// 截断原始响应(前1000字符)
+const truncatedRawResponse = computed(() => {
+  return rawResponse.value.length > 1000 
+    ? rawResponse.value.slice(0, 1000) + '...' 
+    : rawResponse.value;
+});
+
+// ========== 数据请求 & 修复逻辑 ==========
+const fetchData = async () => {
+  try {
+    loading.value = true;
+    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();
+    rawResponse.value = rawText; // 保存原始响应
+
+    // 3. 暴力替换 NaN 为 null(核心修复!)
+    rawText = rawText.replace(/:\s*NaN/g, ': null'); 
+
+    // 4. 解析 JSON(若失败,报错含修复后内容)
+    const geoJSONData = JSON.parse(rawText);
+
+    // 5. 校验 GeoJSON 结构(兜底)
+    if (!geoJSONData.features || !Array.isArray(geoJSONData.features)) {
+      throw new Error('修复后仍无有效 features 数组');
+    }
+
+    // 6. 处理数据(转换经纬度 + 字段兜底)
+    tableData.value = geoJSONData.features
+      .map(feature => feature.properties || {}) // 兜底空 properties
+      .map(props => {
+        // 经纬度转换(按需调整,若不需要可移除)
+        const lng = Number(props.longitude);
+        const lat = Number(props.latitude);
+        if (isNaN(lng) || isNaN(lat)) {
+          console.warn('无效经纬度,跳过该数据:', props);
+          return null;
+        }
+        const [gcjLng, gcjLat] = wgs84togcj02(lng, lat); // 或直接用 lng/lat
+
+        return {
+          id: props.id || '未知',
+          company_name: props.company_name || '未知',
+          company_type: props.company_type || '未知',
+          county: props.county || '未知',
+          particulate_emission: props.particulate_emission !== undefined 
+            ? props.particulate_emission 
+            : '未知',
+          longitude: gcjLng,
+          latitude: gcjLat
+        };
+      })
+      .filter(item => item !== null); // 过滤无效项
+
+  } catch (err) {
+    error.value = `数据加载失败:${err.message}`;
+    console.error('详细错误:', err);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// ========== 生命周期 ==========
+onMounted(() => {
+  fetchData();
+});
+</script>
+
+<style scoped>
+.region-average-chart {
+  width: 100%;
+  margin: 20px 0;
+  padding: 10px;
+}
+
+/* 错误提示 */
+.status.error {
+  color: #dc2626;
+  border: 1px solid #dc2626;
+  padding: 10px;
+  margin-bottom: 10px;
+}
+.status.error button {
+  padding: 2px 6px;
+  cursor: pointer;
+  margin-top: 5px;
+}
+.raw-response pre {
+  white-space: pre-wrap;
+  word-break: break-all;
+  font-size: 12px;
+  margin: 5px 0;
+  padding: 5px;
+  border: 1px solid #ccc;
+}
+
+/* 加载状态 */
+.loading-state {
+  padding: 20px 0;
+  text-align: center;
+}
+.spinner {
+  width: 30px;
+  height: 30px;
+  margin: 0 auto 10px;
+  border: 3px solid #e5e7eb;
+  border-top: 3px solid #333;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 最基础的表格样式 */
+.table-container {
+  overflow-x: auto;
+}
+.data-table {
+  width: 100%;
+  border-collapse: collapse;
+  border: 1px solid #ccc;
+}
+.data-table th, .data-table td {
+  padding: 8px 10px;
+  text-align: center;
+  border: 1px solid #ccc;
+  font-size: 14px;
+}
+.data-table th {
+  font-weight: bold;
+}
+/* 完全移除所有行的背景色和悬停效果 */
+.data-table tr {
+  background-color: transparent;
+}
+.data-table tr:hover {
+  background-color: transparent;
+}
+.empty-state {
+  padding: 20px 0;
+  color: #666;
+}
+</style>

+ 251 - 0
src/components/atmpollution/atmcompanymap.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="map-wrapper">
+    <div ref="mapContainer" class="map-container"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import L from 'leaflet';
+import 'leaflet/dist/leaflet.css';
+
+const mapContainer = ref(null);
+
+// 定义蓝色三角形标记
+const blueTriangle = L.divIcon({
+  className: 'custom-div-icon',
+  html: `<svg width="24" height="24" viewBox="0 0 24 24">
+          <path d="M12 2L2 22h20L12 2z" fill="#0066CC" stroke="#003366" stroke-width="2"/>
+        </svg>`,
+  iconSize: [24, 24],
+  iconAnchor: [12, 24]
+});
+
+onMounted(() => {
+  if (!mapContainer.value) {
+    console.error('❌ 地图容器未找到!');
+    return;
+  }
+
+  const map = L.map(mapContainer.value, {
+    center: [24.7, 114], // 韶关大致中心
+    zoom: 8.5,
+    minZoom: 8,
+  });
+
+
+  // 区县颜色映射(保持不变)
+  const districtColorMap = {
+    "武江区": "#FF6B6B",
+    "浈江区": "#4ECDC4",
+    "曲江区": "#FFD166",
+    "始兴县": "#A0DAA9",
+    "仁化县": "#6A0572",
+    "翁源县": "#1A535C",
+    "乳源瑶族自治县": "#FF9F1C",
+    "新丰县": "#87CEEB",
+    "乐昌市": "#118AB2",
+    "南雄市": "#06D6A0",
+    "韶关市": "#cccccc",
+  };
+
+  // 区县颜色匹配函数(保持不变)
+  function getDistrictColor(name) {
+    if (districtColorMap[name]) return districtColorMap[name];
+    const normalizedName = name.replace(/市|县|区|自治县/g, '');
+    for (const key in districtColorMap) {
+      if (key.includes(normalizedName) || normalizedName.includes(key.replace(/市|县|区|自治县/g, ''))) {
+        return districtColorMap[key];
+      }
+    }
+    return '#cccccc';
+  }
+
+  // 只加载乡镇级GeoJSON,并根据所属区县着色
+  fetch('/data/韶关市乡镇划分图.geojson')
+    .then(res => {
+      if (!res.ok) throw new Error(`乡镇划分图加载失败:${res.status}`);
+      return res.json();
+    })
+    .then(townshipGeojson => {
+      L.geoJSON(townshipGeojson, {
+        style: (feature) => {
+          // 关键:从乡镇数据中获取所属区县信息(假设属性字段为county)
+          // 请根据你的实际GeoJSON属性字段调整(可能是district、parent等)
+          const countyName = feature.properties.FXZQMC || '';
+          
+          return {
+            fillColor: getDistrictColor(countyName), // 使用所属区县的颜色
+            fillOpacity: 0.7,                       // 填充透明度
+            color: '#333333',                       // 边界线颜色
+            weight: 1,                              // 乡镇边界线粗细(比区县细一些)
+            lineJoin: 'round'                       // 线条拐角圆润
+          };
+        },
+        
+      }).addTo(map);
+      console.log('✅ 乡镇划分图加载完成,已按所属区县着色');
+    })
+    .catch(err => {
+      console.error('❌ 乡镇划分图加载失败:', err);
+      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 => {
+      const fixedText = text.replace(
+        /"particulate_emission":\s*NaN/g, 
+        '"particulate_emission": null'
+      );
+      const geoJSONData = JSON.parse(fixedText);
+      
+      console.log('✅ 接口数据加载完成,共', geoJSONData.features.length, '条记录');
+      
+      let markerCount = 0;
+      geoJSONData.features.forEach((feature, idx) => {
+        try {
+          const props = feature.properties;
+          
+          const lng = parseFloat(props.longitude);
+          const lat = parseFloat(props.latitude);
+          if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
+            console.warn(`❌ 无效坐标(第${idx}条):`, lat, lng);
+            return;
+          }
+
+          const emission = props.particulate_emission !== null ? parseFloat(props.particulate_emission) : null;
+          const formattedEmission = emission === null || isNaN(emission)
+            ? '未知' 
+            : `${emission.toFixed(3)} t/a`;
+
+          const marker = L.marker([lat, lng], {
+            icon: blueTriangle,
+            zIndexOffset: 1000,
+          }).addTo(map);
+
+          marker.bindPopup(`
+            <div class="popup-container">
+              <h3 class="popup-title">${props.company_name || '未知'}</h3>
+              <div class="popup-divider"></div>
+              <p><strong>污染源序号:</strong> ${props.id || '未知'}</p>
+              <p><strong>企业类型:</strong> ${props.company_type || '未知'}</p>
+              <p><strong>所属区县:</strong> ${props.county || '未知'}</p>
+              <p><strong>大气颗粒物排放:</strong> ${formattedEmission}</p>
+            </div>
+          `);
+
+          markerCount++;
+        } catch (err) {
+          console.error(`❌ 处理第${idx}条数据失败:`, err);
+        }
+      });
+
+      console.log(`✅ 成功创建 ${markerCount} 个有效标记`);
+    })
+    .catch(err => {
+      console.error('❌ 接口数据加载失败:', err);
+      alert('数据加载错误:' + err.message);
+    });
+
+  map.on('load', () => {
+    setTimeout(() => {
+      map.invalidateSize();
+      console.log('✅ 地图尺寸已重新计算');
+    }, 300);
+  });
+
+  window.addEventListener('resize', () => {
+    map.invalidateSize();
+  });
+});
+</script>
+
+<style scoped>
+/* 样式保持不变 */
+.map-wrapper {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.map-container {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+/* 弹窗样式 */
+::v-deep .popup-title {
+  text-align: center;
+  font-size: 18px;
+  font-weight: 700;
+  color: #0066CC;
+  margin: 0 0 6px;
+  border-bottom: none;
+  padding-bottom: 8px;
+}
+
+::v-deep .popup-divider {
+  height: 1px;
+  background: #0066CC;
+  margin: 8px 0;
+}
+
+::v-deep .popup-container {
+  min-width: 240px;
+  max-width: 300px;
+  padding: 16px;
+  font-family: "Microsoft YaHei", sans-serif;
+}
+
+::v-deep .popup-container p {
+  margin: 6px 0;
+  font-size: 15px;
+  color: #666;
+  line-height: 1.6;
+}
+
+::v-deep .popup-container strong {
+  color: #0066CC;
+  font-weight: 600;
+}
+
+::v-deep .exceeding {
+  color: #FF3333;
+  font-weight: bold;
+}
+
+/* 美化弹窗 */
+::v-deep .leaflet-popup-content-wrapper {
+  padding: 0 !important;
+  border-radius: 12px !important;
+  box-shadow: 0 6px 16px rgba(0,0,0,0.2) !important;
+}
+
+::v-deep .leaflet-popup-content {
+  margin: 0 !important;
+  width: auto !important;
+}
+
+::v-deep .leaflet-popup-tip {
+  display: none;
+}
+
+/* 图例样式 */
+::v-deep .info {
+  padding: 6px 8px;
+  background: white;
+  background: rgba(255,255,255,0.9);
+  box-shadow: 0 0 15px rgba(0,0,0,0.2);
+  border-radius: 5px;
+}
+
+/* 自定义标记样式 */
+::v-deep .custom-div-icon svg {
+  transition: transform 0.2s;
+  display: block;
+}
+</style>

+ 377 - 0
src/components/atmpollution/atmsamplemap.vue

@@ -0,0 +1,377 @@
+<template>
+  <div class="map-wrapper">
+    <div ref="mapContainer" class="map-container"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, watch } from 'vue'; 
+import L from 'leaflet';
+import 'leaflet/dist/leaflet.css';
+
+const props = defineProps({
+  calculationMethod: {
+    type: String,
+    required: true,
+    default: 'weight' 
+  }
+});
+
+const mapContainer = ref(null);
+const mapInstance = ref(null); 
+const markers = ref([]); 
+
+// ====================== 核心:重量→体积换算逻辑 ======================
+function calculateConcentration(heavyMetalWeight, volume) {
+  if (heavyMetalWeight === undefined || volume === undefined || isNaN(heavyMetalWeight) || isNaN(volume) || volume === 0) {
+    return '未知';
+  }
+  const ug = heavyMetalWeight * 1000; 
+  const concentration = ug / volume;
+  return concentration.toFixed(2); 
+}
+
+function calculateParticleConcentration(particleWeight, volume) {
+  if (particleWeight === undefined || volume === undefined || isNaN(particleWeight) || isNaN(volume) || volume === 0) {
+    return '未知';
+  }
+  const ug = particleWeight * 1000; 
+  const concentration = ug / volume;
+  return concentration.toFixed(2);
+}
+
+// ====================== 指标映射(重量直接取数,体积动态计算) ======================
+const metricsMap = {
+  weight: [ 
+    { label: 'Cr mg/kg', key: 'Cr_particulate' },
+    { label: 'As mg/kg', key: 'As_particulate' },
+    { label: 'Cd mg/kg', key: 'Cd_particulate' },
+    { label: 'Hg mg/kg', key: 'Hg_particulate' },
+    { label: 'Pb mg/kg', key: 'Pb_particulate' },
+    { label: '颗粒物重量 mg', key: 'particle_weight' }
+  ],
+  volume: [ 
+    { label: 'Cr ug/m³', getValue: (item) => calculateConcentration(item.Cr_particulate, item.standard_volume) },
+    { label: 'As ug/m³', getValue: (item) => calculateConcentration(item.As_particulate, item.standard_volume) },
+    { label: 'Cd ug/m³', getValue: (item) => calculateConcentration(item.Cd_particulate, item.standard_volume) },
+    { label: 'Hg ug/m³', getValue: (item) => calculateConcentration(item.Hg_particulate, item.standard_volume) },
+    { label: 'Pb ug/m³', getValue: (item) => calculateConcentration(item.Pb_particulate, item.standard_volume) },
+    { label: '颗粒物浓度 ug/m³', getValue: (item) => calculateParticleConcentration(item.particle_weight, item.standard_volume) },
+    { label: '标准体积 m³', key: 'standard_volume' },
+  ]
+};
+
+// ====================== 工具函数 ======================
+function formatValue(value) {
+  if (value === undefined || value === null || value === '' || isNaN(value)) {
+    return '未知';
+  }
+  return parseFloat(value);
+}
+
+function formatLocation(fullLocation) {
+  if (!fullLocation) return '未知位置';
+  const processed = fullLocation.replace(/^广东省韶关市/, '').trim().replace(/^韶关市/, '');
+  return processed || '未知位置';
+}
+
+function generatePopupContent(item, method) {
+  const metrics = metricsMap[method]; 
+  const metricsHtml = metrics.map(metric => {
+    let value;
+    if (method === 'weight') {
+      value = formatValue(item[metric.key]);
+    } else {
+      value = metric.getValue ? metric.getValue(item) : formatValue(item[metric.key]);
+    }
+    return `
+      <div class="data-item">
+        <span class="item-label">${metric.label}:</span>
+        <span class="item-value">${value}</span>
+      </div>
+    `;
+  }).join('');
+
+  return `
+    <div class="popup-container">
+      <div class="popup-header">
+        <h3 class="popup-title">${formatLocation(item.sampling_location || '未知采样点')}</h3>
+      </div>
+      <ul class="popup-info-list">
+        <li>
+          <span class="info-label">采样点ID:</span>
+          <span class="info-value">${item.ID || item.sample_code || '未知'}</span>
+        </li>
+        <li>
+          <span class="info-label">样品名称:</span>
+          <span class="info-value">${item.sample_name || '未知'}</span>
+        </li>
+      </ul>
+      <div class="grid-container">
+        <div class="grid-item">${metricsHtml}</div>
+      </div>
+    </div>
+  `;
+}
+
+// ====================== 区县名称归一化(处理后缀差异) ======================
+function normalizeDistrictName(name) {
+  if (!name) return '';
+  return name.replace(/市|县|区|自治县/g, ''); // 移除行政后缀
+}
+
+onMounted(() => {
+  if (!mapContainer.value) {
+    console.error('❌ 地图容器未找到!');
+    return;
+  }
+
+  const map = L.map(mapContainer.value, {
+    center: [24.7, 114], 
+    zoom: 8.5,
+    minZoom: 8.3,
+  });
+  mapInstance.value = map;
+
+  // 区县颜色映射(键名与GeoJSON的FXZQMC精确匹配,或归一化后匹配)
+  const districtColorMap = {
+    "武江区": "#FF6B6B",
+    "浈江区": "#4ECDC4",
+    "曲江区": "#FFD166",
+    "始兴县": "#A0DAA9",
+    "仁化县": "#6A0572",
+    "翁源县": "#1A535C",
+    "乳源瑶族自治县": "#FF9F1C", // 支持精确匹配
+    "新丰县": "#87CEEB",
+    "乐昌市": "#118AB2",
+    "南雄市": "#06D6A0",
+    "乳源": "#FF9F1C" // 可选:支持归一化后匹配(如GeoJSON是“乳源”时)
+  };
+
+  // 加载区县边界(关键修改:匹配FXZQMC字段 + 归一化)
+  fetch('/data/韶关市乡镇划分图.geojson')
+    .then(res => {
+      if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
+      return res.json();
+    })
+    .then(geojson => {
+      console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
+      
+      L.geoJSON(geojson, {
+        style: (feature) => {
+          const rawDistrictName = feature.properties.FXZQMC || ''; // 从FXZQMC提取
+          const normalizedName = normalizeDistrictName(rawDistrictName);
+          
+          // 优先精确匹配,再归一化匹配
+          const color = districtColorMap[rawDistrictName] || 
+                        districtColorMap[normalizedName] || 
+                        '#cccccc';
+          
+          return {
+            fillColor: color,
+            fillOpacity: 0.7,
+            color: '#333333',
+            weight: 2,
+          };
+        },
+      }).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 => {
+          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;
+                }
+              }
+
+              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(err => {
+          console.error('❌ 大气数据加载失败:', err);
+          alert('大气数据接口错误:' + err.message);
+        });
+    })
+    .catch(err => {
+      console.error('❌ 区县边界加载失败:', err);
+      alert('区县边界加载错误:' + err.message);
+    });
+});
+
+// 监听计算方式变化,更新弹窗
+watch(
+  () => props.calculationMethod,
+  (newMethod) => {
+    markers.value.forEach(({ marker, item }) => {
+      marker.bindPopup(generatePopupContent(item, newMethod));
+    });
+    console.log(`✅ 已切换为${newMethod === 'weight' ? '重量' : '体积'}计算方式,弹窗内容已更新`);
+  }
+);
+</script>
+
+<style scoped>
+.map-wrapper {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.map-container {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+::v-deep .leaflet-popup-content-wrapper {
+  padding: 0 !important;
+  border-radius: 10px !important;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
+}
+
+::v-deep .leaflet-popup-content {
+  margin: 0 !important;
+  width: auto !important;
+  max-width: 300px; 
+}
+
+::v-deep .popup-container {
+  min-width: 280px;
+  padding: 12px;
+  font-family: "Microsoft YaHei", sans-serif;
+}
+
+::v-deep .popup-header {
+  margin-bottom: 10px;
+}
+
+::v-deep .popup-title {
+  text-align: center;
+  font-size: 16px;
+  font-weight: 700;
+  color: #0066CC;
+  margin: 0 0 5px;
+  padding-bottom: 6px;
+  border-bottom: 1.5px solid #0066CC;
+}
+
+::v-deep .popup-info-list {
+  list-style: none;
+  padding: 0;
+  margin: 0 0 10px;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 6px;
+}
+
+::v-deep .popup-info-list li {
+  display: flex;
+  margin: 0;
+  padding: 3px 6px;
+  background: #f9f9f9;
+  border-radius: 3px;
+}
+
+::v-deep .info-label {
+  flex: 0 0 85px;
+  font-weight: 600;
+  color: #333;
+  font-size: 13px;
+}
+
+::v-deep .info-value {
+  flex: 1;
+  color: #666;
+  font-size: 13px;
+  white-space: nowrap;
+}
+
+::v-deep .grid-container {
+  display: grid;
+  grid-template-columns: 1fr; 
+  gap: 6px;
+}
+
+::v-deep .grid-item {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+::v-deep .data-item {
+  display: flex;
+  justify-content: space-between;
+  padding: 6px 8px;
+  background: #f9f9f9;
+  border-radius: 3px;
+}
+
+::v-deep .item-label {
+  font-weight: 600;
+  color: #555;
+  font-size: 13px;
+}
+
+::v-deep .item-value {
+  color: #000;
+  font-size: 13px;
+}
+
+::v-deep .leaflet-popup-tip {
+  display: none;
+}
+
+::v-deep .leaflet-circle-marker {
+  stroke-width: 1.5px !important;
+}
+
+::v-deep .leaflet-marker-pane .leaflet-circle-marker[fill="#118AB2"]:hover {
+  fill-opacity: 1 !important;
+  stroke-width: 2.5px !important;
+}
+</style>

+ 392 - 0
src/components/atmpollution/heavyMetalEnterprisechart.vue

@@ -0,0 +1,392 @@
+<template>
+  <div class="heavy-metal-chart">
+    <!-- 错误提示(带原始响应预览) -->
+    <div v-if="error" class="status error">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+      <div class="raw-response" v-if="rawResponse">
+        <button @click="showRaw = !showRaw" class="raw-btn">
+          {{ showRaw ? '收起原始响应' : '查看原始响应(前1000字符)' }}
+        </button>
+        <pre v-if="showRaw" class="raw-pre">{{ truncatedRawResponse }}</pre>
+      </div>
+    </div>
+
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-state">
+      <div class="spinner"></div>
+      <p>数据加载中...</p>
+    </div>
+
+    <!-- 图表容器(使用v-show确保DOM始终存在) -->
+    <div 
+      v-show="!loading && !error" 
+      ref="chartRef" 
+      class="chart-box"
+      :style="{ 
+        height: '500px', 
+        border: '2px solid #1890ff', 
+        position: 'relative' 
+      }"
+    >
+      <!-- 容器状态可视化提示(调试用) -->
+      <div class="container-status" v-if="debugMode">
+        容器状态: {{ containerStatus }}
+        <br>
+        高度: {{ containerHeight }}px
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, computed, onUnmounted, nextTick } from 'vue';
+import * as echarts from 'echarts';
+import axios from 'axios';
+
+// ========== 核心配置 ==========
+const API_URL = 'http://localhost:8000/api/vector/export/all?table_name=atmo_company'; 
+const SG_REGIONS = [
+  '浈江区', '武江区', '曲江区', '乐昌市', 
+  '南雄市', '始兴县', '仁化县', '翁源县', 
+  '新丰县', '乳源县'
+];
+const EXCLUDE_FIELDS = [
+  'id', 'company_name', 'company_type', 'longitude', 'latitude'
+];
+const debugMode = true; // 调试模式:显示容器状态
+
+// ========== 响应式数据 ==========
+const chartRef = ref(null);
+const error = ref('');
+const loading = ref(true);
+const rawResponse = ref('');
+const showRaw = ref(false);
+const containerStatus = ref('未初始化');
+const containerHeight = ref(0);
+let myChart = null;
+
+// 截断原始响应
+const truncatedRawResponse = computed(() => {
+  return rawResponse.value.length > 1000 
+    ? rawResponse.value.slice(0, 1000) + '...' 
+    : rawResponse.value;
+});
+
+// ========== 容器状态检查(实时更新) ==========
+const checkContainer = () => {
+  if (!chartRef.value) {
+    containerStatus.value = '未找到容器元素';
+    containerHeight.value = 0;
+    return false;
+  }
+  
+  containerStatus.value = '已找到容器元素';
+  containerHeight.value = chartRef.value.offsetHeight;
+  return true;
+};
+
+// ========== 数据处理逻辑 ==========
+const processData = (features) => {
+  console.log('🔍 开始处理数据,features数量:', features.length);
+  
+  // 提取有效properties
+  const apiData = features
+    .map(feature => feature.properties || {})
+    .filter(props => Object.keys(props).length > 0);
+  console.log('🔍 有效properties数量:', apiData.length);
+
+  if (apiData.length === 0) {
+    throw new Error('无有效数据(properties为空)');
+  }
+
+  // 识别污染物字段
+  const pollutantFields = Object.keys(apiData[0])
+    .filter(key => 
+      !EXCLUDE_FIELDS.includes(key) &&  
+      !isNaN(parseFloat(apiData[0][key]))  
+    );
+  console.log('🔍 识别的污染物字段:', pollutantFields);
+
+  if (pollutantFields.length === 0) {
+    throw new Error('未识别到有效污染物字段,请检查EXCLUDE_FIELDS');
+  }
+
+  // 按区县统计
+  const regionStats = {};
+  const globalStats = {};
+  let totalSamples = 0;
+
+  pollutantFields.forEach(field => {
+    globalStats[field] = { sum: 0, count: 0 };
+  });
+
+  apiData.forEach(item => {
+    const county = item.county || '未知区县';
+    totalSamples++;
+
+    if (!regionStats[county]) {
+      regionStats[county] = {};
+      pollutantFields.forEach(field => {
+        regionStats[county][field] = { sum: 0, count: 0 };
+      });
+    }
+
+    pollutantFields.forEach(field => {
+      const value = parseFloat(item[field]);
+      if (!isNaN(value)) {
+        regionStats[county][field].sum += value;
+        regionStats[county][field].count++;
+        globalStats[field].sum += value;
+        globalStats[field].count++;
+      }
+    });
+  });
+  console.log('🔍 区县统计结果:', regionStats);
+
+  // 构建有效区县
+  const validRegions = SG_REGIONS.filter(region => regionStats[region])
+    .concat('全市平均');
+  console.log('🔍 有效区县列表:', validRegions);
+
+  // 构建图表数据
+  const series = pollutantFields.map((field, index) => ({
+    name: field,
+    type: 'bar',
+    data: validRegions.map(region => {
+      if (region === '全市平均') {
+        return globalStats[field].count 
+          ? (globalStats[field].sum / globalStats[field].count).toFixed(4) 
+          : 0;
+      }
+      return regionStats[region][field].count 
+        ? (regionStats[region][field].sum / regionStats[region][field].count).toFixed(4) 
+        : 0;
+    }),
+    itemStyle: { 
+  // 当x轴类目是“全市平均”时,柱子显示红色
+  color: (params) => {
+    // params.dataIndex 对应 regions 数组的索引,最后一个是“全市平均”
+    const isTotal = validRegions[params.dataIndex] === '全市平均';
+    return isTotal ? '#ff0000' : '#1890ff'; // 红色可用 #ff0000 或其他红色值
+  }
+},
+    label: { show: true, position: 'top', fontSize: 15 }
+  }));
+
+  return { regions: validRegions, series, totalSamples };
+};
+
+// ========== ECharts初始化 ==========
+const initChart = (data) => {
+  // 检查容器状态
+  if (!checkContainer()) {
+    error.value = '图表容器未准备好,请刷新页面重试';
+    return;
+  }
+
+  // 检查容器高度
+  if (containerHeight.value < 100) {
+    error.value = `容器高度异常(${containerHeight.value}px),请检查样式`;
+    return;
+  }
+
+  // 销毁旧实例
+  if (myChart && !myChart.isDisposed()) {
+    myChart.dispose();
+  }
+
+  // 空数据检查
+  if (data.series.length === 0 || data.regions.length === 0) {
+    error.value = '无有效数据用于绘制图表';
+    return;
+  }
+
+  // 初始化图表
+  try {
+    myChart = echarts.init(chartRef.value);
+    myChart.setOption({
+      title: {
+        text: '韶关市各区县企业污染物平均值',
+        left: 'center',
+        subtext: `基于 ${data.totalSamples} 个有效样本`,
+        subtextStyle: { fontSize: 15 }
+      },
+      tooltip: {
+        trigger: 'axis',
+        formatter: (params) => {
+          let content = `${params[0].name}:<br>`;
+          params.forEach(p => {
+            content += `${p.seriesName}: ${p.value} t/a<br>`;
+          });
+          return content;
+        },
+        axisLabel:{fontSize:15}
+      },
+      xAxis: {
+        type: 'category',
+        data: data.regions,
+        axisLabel: { rotate: 30, fontSize: 15 }
+      },
+      yAxis: {
+        type: 'value',
+        name: '浓度 (t/a)',
+        nameTextStyle: { fontSize: 15 },
+        axisLabel:{fontSize:15},
+      },
+      
+      series: data.series,
+      grid: { left: '5%', right: '5%', bottom: '20%', containLabel: true }
+    });
+    console.log('✅ 图表初始化成功');
+  } catch (err) {
+    error.value = `图表初始化失败:${err.message}`;
+    console.error('图表初始化错误:', err);
+  }
+};
+
+// ========== 数据请求逻辑 ==========
+const fetchData = async () => {
+  try {
+    loading.value = true;
+    error.value = '';
+    console.log('🚀 开始请求数据:', API_URL);
+
+    // 发起请求(延长超时)
+    const response = await axios.get(API_URL, {
+      timeout: 20000, // 20秒超时
+      responseType: 'text'
+    });
+    rawResponse.value = response.data;
+    console.log('✅ 数据请求成功,状态码:', response.status);
+
+    // 修复NaN并解析
+    const fixedJson = response.data.replace(/:\s*NaN/g, ': null');
+    const geoJSONData = JSON.parse(fixedJson);
+
+    // 校验数据结构
+    if (!geoJSONData.features || !Array.isArray(geoJSONData.features)) {
+      throw new Error('响应数据缺少features数组');
+    }
+
+    // 处理数据
+    const chartData = processData(geoJSONData.features);
+    console.log('✅ 数据处理完成,准备渲染图表');
+
+    // 等待DOM更新(双重保险)
+    await nextTick();
+    console.log('🔄 DOM更新完成,检查容器:', chartRef.value);
+
+    // 强制延迟确保容器准备好(极端情况处理)
+    setTimeout(() => {
+      initChart(chartData);
+    }, 300);
+
+  } catch (err) {
+    error.value = `数据加载失败:${err.message}`;
+    console.error('❌ 数据请求错误:', err);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// ========== 生命周期 ==========
+onMounted(() => {
+  // 初始检查容器
+  checkContainer();
+  // 开始加载数据
+  fetchData();
+});
+
+// ========== 响应式布局 ==========
+const handleResize = () => {
+  if (myChart) {
+    myChart.resize();
+    console.log('🔄 图表已重绘');
+  }
+};
+onMounted(() => window.addEventListener('resize', handleResize));
+onUnmounted(() => window.removeEventListener('resize', handleResize));
+</script>
+
+<style scoped>
+.heavy-metal-chart {
+  width: 90%;
+  max-width: 1200px;
+  margin: 20px auto;
+  padding: 20px;
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+  position: relative;
+}
+
+/* 错误提示 */
+.status.error {
+  color: #dc2626;
+  background: #fee2e2;
+  padding: 12px 16px;
+  border-radius: 6px;
+  margin-bottom: 16px;
+}
+.raw-btn {
+  margin: 8px 0;
+  padding: 4px 8px;
+  background: #ff4d4f;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 18px;
+}
+.raw-pre {
+  white-space: pre-wrap;
+  word-break: break-all;
+  background: #f9fafb;
+  padding: 8px;
+  border-radius: 4px;
+  font-size: 18px;
+  max-height: 200px;
+  overflow: auto;
+}
+
+/* 加载状态 */
+.loading-state {
+  text-align: center;
+  padding: 60px 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); }
+}
+
+/* 图表容器 */
+.chart-box {
+  width: 100%;
+  min-height: 500px !important; /* 强制最小高度 */
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+/* 容器状态提示 */
+.container-status {
+  position: absolute;
+  top: 10px;
+  left: 10px;
+  background: rgba(255,255,255,0.8);
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  color: #1890ff;
+  z-index: 10;
+}
+</style>

+ 213 - 0
src/components/irrpollution/crossSectionSamplelineData.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="map-page">
+    <!-- 错误提示 -->
+    <div v-if="error" class="error-message">
+      <i class="fa fa-exclamation-circle"></i> {{ error }}
+    </div>
+    
+    <!-- 加载状态 -->
+    <div v-if="loading" class="loading-state">
+      <div class="spinner"></div>
+      <p>数据加载中...</p>
+    </div>
+
+    <!-- 数据表格容器 -->
+    <div v-else class="table-container">
+      <table class="data-table">
+        <!-- 表头 -->
+        <thead>
+          <tr>
+            <th>断面编号</th>
+            <th>所属河流</th>
+            <th>断面位置</th>
+            <th>所属区县</th>
+            <th>Cd含量(ug/L)</th>
+            <th>经度</th>
+            <th>纬度</th>
+          </tr>
+        </thead>
+        <!-- 表体(遍历数据) -->
+        <tbody>
+          <tr v-for="item in state.excelData" :key="item.id">
+            <td>{{ item.id }}</td>
+            <td>{{ item.river }}</td>
+            <td>{{ item.location }}</td>
+            <td>{{ item.district }}</td>
+            <td>{{ item.cdValue }}</td>
+            <td>{{ item.longitude.toFixed(6) }}</td> <!-- 保留6位小数 -->
+            <td>{{ item.latitude.toFixed(6) }}</td>
+          </tr>
+          <!-- 空数据提示 -->
+          <tr v-if="state.excelData.length === 0">
+            <td colspan="7" class="empty-state">暂无数据</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { wgs84togcj02 } from 'coordtransform';
+
+// 状态管理
+const error = ref(null)
+const loading = ref(true)
+const state = reactive({
+  excelData: [], // 存储解析后的断面数据
+  riverAvgData: [] // 存储按河流分组后的平均数据
+})
+
+// 从接口获取数据并处理
+const fetchData = 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(`接口请求失败: ${response.status}`)
+    }
+    
+    // 解析GeoJSON数据
+    const geoJsonData = await response.json()
+    
+    // 处理数据
+    state.excelData = geoJsonData.features
+      .map(feature => {
+        const props = feature.properties
+        // 转换经纬度
+        const lng = Number(props.longitude)
+        const lat = Number(props.latitude)
+        
+        if (isNaN(lat) || isNaN(lng)) {
+          console.error('无效经纬度数据:', props)
+          return null
+        }
+        
+        // WGS84转GCJ02坐标
+        const [gcjLng, gcjLat] = wgs84togcj02(lng, lat)
+        
+        return {
+          id: props.id,
+          river: props.river_name || '未知河流',
+          location: props.position || '未知位置',
+          district: props.county || '未知区县',
+          cdValue: props.cd_concentration !== undefined ? props.cd_concentration : '未知',
+          latitude: gcjLat,
+          longitude: gcjLng
+        }
+      })
+      .filter(item => item !== null) // 过滤无效数据
+      
+  } catch (err) {
+    error.value = `数据加载失败: ${err.message}`
+    console.error('数据处理错误:', err)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 生命周期
+onMounted(async () => {
+  await fetchData()
+})
+
+</script>
+
+<style scoped>
+.map-page {
+  width: 100%;
+  margin: 0 auto 24px;
+  background-color: white;
+  border-radius: 12px;
+  padding: 20px;
+  box-sizing: border-box;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 错误提示样式 */
+.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;
+}
+
+/* 加载状态样式 */
+.loading-state {
+  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); }
+}
+
+.data-table {
+  width: 100%;
+  border-collapse: collapse;
+  min-width: 800px;
+}
+
+.data-table th, .data-table td {
+  padding: 12px 15px;
+  text-align: center;
+  border: 1px solid #e5e7eb;
+}
+
+.data-table th {
+  background-color: #f3f4f6;
+  font-weight: bold;
+  color: #1f2937;
+}
+
+.data-table tr:nth-child(even) {
+  background-color: #f9fafb;
+}
+
+.data-table tr:hover {
+  background-color: #f3f4f6;
+}
+
+/* 空数据状态 */
+.empty-state {
+  padding: 40px 0;
+  color: #6b7280;
+  font-style: italic;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .map-page {
+    width: 96%;
+  }
+  
+  .table-container {
+    overflow-x: auto;
+  }
+}
+</style>

+ 262 - 0
src/components/irrpollution/crossSetionData1.vue

@@ -0,0 +1,262 @@
+<template>
+  <div class="chart-page">
+    <!-- 加载状态 -->
+    <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 { wgs84togcj02 } from 'coordtransform';
+import * as echarts from 'echarts'
+
+// 状态管理
+const error = ref(null)
+const loading = ref(true)  // 新增加载状态
+const chartContainer = ref(null)
+let chart = null
+
+const state = reactive({
+  excelData: [],        // 存储解析后的断面数据
+  riverAvgData: []      // 存储按河流分组后的平均数据
+})
+
+// 从接口获取数据并初始化
+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}`)
+    }
+    
+    // 解析GeoJSON数据
+    const geoJsonData = await response.json()
+    
+    // 处理每个Feature的properties
+    state.excelData = geoJsonData.features
+      .map(feature => {
+        const props = feature.properties
+        
+        // 处理经纬度(保持原有坐标转换逻辑)
+        const lng = Number(props.longitude)
+        const lat = Number(props.latitude)
+        
+        if (isNaN(lat) || isNaN(lng)) {
+          console.error('无效经纬度数据:', props)
+          return null
+        }
+        
+        // WGS84转GCJ02坐标
+        const [gcjLng, gcjLat] = wgs84togcj02(lng, lat)
+        
+        return {
+          id: props.id,
+          river: props.river_name || '未知河流',
+          location: props.position || '未知位置',
+          district: props.county || '未知区县',
+          cdValue: props.cd_concentration !== undefined ? props.cd_concentration : '未知',
+          latitude: gcjLat,
+          longitude: gcjLng
+        }
+      })
+      .filter(item => item !== null) // 过滤无效数据
+    
+    // 计算河流平均值
+    calculateRiverAvg()
+    
+  } catch (err) {
+    error.value = `数据加载失败:${err.message}`
+    console.error('数据处理错误:', err)
+  } finally {
+    loading.value = false
+  }
+}
+
+// 按河流分组计算平均值
+const calculateRiverAvg = () => {
+  const riverGroups = {};
+
+  // 分组统计
+  state.excelData.forEach(item => {
+    if (!riverGroups[item.river]) {
+      riverGroups[item.river] = { total: 0, count: 0 }
+    }
+    riverGroups[item.river].total += item.cdValue
+    riverGroups[item.river].count += 1
+  });
+
+  // 计算各组平均值
+  const riverAvg = [];
+  let totalAll = 0;
+  let countAll = 0;
+
+  for (const river in riverGroups) {
+    const avg = riverGroups[river].total / riverGroups[river].count;
+    riverAvg.push({
+      river,
+      avg: parseFloat(avg) // 保留3位小数
+    });
+    totalAll += riverGroups[river].total;
+    countAll += riverGroups[river].count;
+  }
+
+  // 添加总平均值
+  const totalAvg = {
+    river: '总河流平均',
+    avg: parseFloat((totalAll / countAll))
+  };
+  riverAvg.push(totalAvg);
+
+  state.riverAvgData = riverAvg;
+  updateChart(); // 更新图表
+}
+
+// 初始化ECharts实例
+const initChart = () => {
+  if (chartContainer.value) {
+    chart = echarts.init(chartContainer.value)
+    updateChart()
+  }
+}
+
+// 更新图表数据
+const updateChart = () => {
+  if (!chart || state.riverAvgData.length === 0) return;
+
+  // 处理图表数据
+  const rivers = state.riverAvgData.map(item => item.river)
+  const avgs = state.riverAvgData.map(item => item.avg)
+
+  // ECharts配置项
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: '{a} <br/>{b}: {c} ug/L'
+    },
+    grid: {
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: rivers,
+      axisLabel: { interval: 0, fontSize: 15 }
+    },
+    yAxis: {
+      type: 'value',
+      name: 'Cd浓度 (ug/L)',
+      min: 0,
+      nameTextStyle: { fontSize: 15},
+      axisLabel: { formatter: '{value}', fontSize: 15 }
+    },
+    series: [{
+      name: '平均镉浓度',
+      type: 'bar',
+      data: avgs,
+      itemStyle: {
+        color: (params) => 
+          params.dataIndex === rivers.length - 1 ? '#FF4500' : '#1E88E5'
+      },
+      label: {
+        show: true,
+        position: 'top',
+        formatter: '{c}',
+        fontSize: 15
+      },
+      emphasis: { focus: 'series' }
+    }]
+  };
+
+  chart.setOption(option)
+}
+
+// 生命周期钩子
+onMounted(async () => {
+  await initData()   // 先加载数据
+  initChart()        // 再初始化图表
+  
+  // 监听窗口变化
+  window.addEventListener('resize', () => {
+    if (chart) chart.resize()
+  })
+})
+
+onBeforeUnmount(() => {
+  if (chart) chart.dispose() // 销毁图表实例
+})
+</script>
+
+<style scoped>
+.chart-page {
+  width: 100%;
+  margin: 0 auto 24px;
+  background-color: white;
+  border-radius: 12px;
+  padding: 20px;
+  box-sizing: border-box;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.chart-container {
+  width: 100%; 
+  height: 400px;
+  margin: 0 auto;
+  border-radius: 12px;
+}
+
+/* 加载状态样式 */
+.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>

+ 79 - 50
src/views/User/HmOutFlux/irrigationWater/crossSetionData2.vue → src/components/irrpollution/crossSetionData2.vue

@@ -17,71 +17,79 @@ const state = reactive({
   districtAvgData: [] // 存储按区县分组后的平均数据
 })
 
-// 初始化数据(移除坐标转换,只保留必要字段)
-const initData = () => {
-  const rawData = [
-    { "断面编号": 0, "所属河流": "浈江", "断面位置": "小古录", "所属区县": "始兴县", "Cd(ug/L)": 0.11 },
-    { "断面编号": 1, "所属河流": "浈江", "断面位置": "长坝", "所属区县": "仁化县", "Cd(ug/L)": 1.116 },
-    { "断面编号": 2, "所属河流": "浈江", "断面位置": "东河桥", "所属区县": "浈江区", "Cd(ug/L)": 3.46 },
-    { "断面编号": 3, "所属河流": "武江", "断面位置": "坪石", "所属区县": "乐昌市", "Cd(ug/L)": 0.98 },
-    { "断面编号": 4, "所属河流": "武江", "断面位置": "乐昌", "所属区县": "乐昌市", "Cd(ug/L)": 0.11 },
-    { "断面编号": 5, "所属河流": "武江", "断面位置": "武江桥", "所属区县": "乐昌市", "Cd(ug/L)": 0.15 },
-    { "断面编号": 6, "所属河流": "北江", "断面位置": "九公里", "所属区县": "浈江区", "Cd(ug/L)": 7.83 },
-    { "断面编号": 7, "所属河流": "北江", "断面位置": "白土", "所属区县": "曲江区", "Cd(ug/L)": 5.94 },
-    { "断面编号": 8, "所属河流": "浈江", "断面位置": "昆仑水站", "所属区县": "南雄市", "Cd(ug/L)": 0.517 },
-    { "断面编号": 9, "所属河流": "北江", "断面位置": "白沙", "所属区县": "曲江区", "Cd(ug/L)": 1.54 },
-    { "断面编号": 10, "所属河流": "浈江", "断面位置": "周田水站", "所属区县": "仁化县", "Cd(ug/L)": 0.182 },
-    { "断面编号": 11, "所属河流": "武江", "断面位置": "坪石水站", "所属区县": "乐昌市", "Cd(ug/L)": 1.071 }
-  ];
-
-  // 处理数据:只保留柱状图需要的字段(移除经纬度和坐标转换)
-  state.excelData = rawData.map(item => ({
-    id: item.断面编号,
-    river: item.所属河流,
-    district: item.所属区县, // 区县字段(核心)
-    cdValue: item["Cd(ug/L)"] // 浓度值(核心)
-  }));
-
-  calculateDistrictAvg(); // 计算区县平均值(替换原河流分组逻辑)
+// 从接口初始化数据(核心修改:异步获取 + 严格数据解析)
+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})`)
+    }
+    
+    const geoJson = await response.json()
+    
+    // 逐行解析Feature,确保每个样本都被处理
+    state.excelData = geoJson.features.map(feature => {
+      const props = feature.properties || {}
+      
+      // 强制转换Cd浓度为数值(处理异常值)
+      let cdValue = parseFloat(props.cd_concentration)
+      if (isNaN(cdValue)) {
+        console.warn('发现无效Cd浓度值,已设为0:', props)
+        cdValue = 0 // 保证参与计算,避免数据缺失
+      }
+      
+      return {
+        id: props.id || '未知ID',       // 兜底处理
+        river: props.river_name || '未知河流',
+        district: props.county || '未知区县',
+        cdValue: cdValue
+      }
+    })
+    
+    calculateDistrictAvg() // 计算区县平均值
+  } catch (err) {
+    console.error('数据加载失败:', err)
+    // 可扩展:全局错误提示
+  }
 }
 
-// 按区县计算平均浓度(核心逻辑)
+// 按区县计算平均浓度(核心逻辑保持不变
 const calculateDistrictAvg = () => {
-  // 1. 按区县分组:累加每个区县的浓度总和和断面数量
   const districtGroups = {};
+  
+  // 1. 分组统计(总和 + 数量)
   state.excelData.forEach(item => {
     const district = item.district;
     if (!districtGroups[district]) {
-      districtGroups[district] = {
-        total: 0, // 浓度总和
-        count: 0  // 断面数量
-      };
+      districtGroups[district] = { total: 0, count: 0 };
     }
     districtGroups[district].total += item.cdValue;
     districtGroups[district].count += 1;
   });
 
-  // 2. 计算每个区县的平均值
+  // 2. 计算平均值 + 总平均
   const districtAvg = [];
-  let totalAll = 0; // 所有区县总浓度
-  let countAll = 0; // 所有区县总断面数
+  let totalAll = 0;
+  let countAll = 0;
 
   for (const district in districtGroups) {
     const avg = districtGroups[district].total / districtGroups[district].count;
     districtAvg.push({
-      district, // 区县名称
-      avg: parseFloat(avg.toFixed(3)) // 保留3位小数
+      district,
+      avg: parseFloat(avg) // 保留3位小数
     });
     totalAll += districtGroups[district].total;
     countAll += districtGroups[district].count;
   }
 
-  // 3. 计算总平均值(可选,用于对比
-  const totalAvg = {
+  // 添加总平均值(最后一项
+  districtAvg.push({
     district: '总平均',
-    avg: parseFloat((totalAll / countAll).toFixed(3))
-  };
-  districtAvg.push(totalAvg);
+    avg: parseFloat((totalAll / countAll))
+  });
 
   state.districtAvgData = districtAvg;
   updateChart(); // 更新图表
@@ -120,7 +128,7 @@ const updateChart = () => {
       data: districts,
       axisLabel: {
         interval: 0,
-        fontSize: 18
+        fontSize: 15
       }
     },
     yAxis: {
@@ -128,11 +136,11 @@ const updateChart = () => {
       name: 'Cd浓度 (μg/L)',
       min: 0, // 从0开始更直观
       nameTextStyle:{
-        fontSize:18,
+        fontSize:15,
       },
       axisLabel: {
         formatter: '{value}',
-        fontSize: 18
+        fontSize: 15
       }
     },
     series: [
@@ -148,10 +156,23 @@ const updateChart = () => {
           show: true, // 显示数值标签
           position: 'top',
           formatter: '{c}',
-          fontSize: 18
+          fontSize: 15
         },
         barWidth: '60%'
       }
+    ],
+    graphic: [
+      {
+        type: 'rect',
+        left: '5%',   // 与 grid.left 对齐
+        right: '5%',  // 与 grid.right 对齐
+        bottom: '15%',// 与 grid.bottom 对齐(位于绘图区域底部)
+        height: 30,   // 圆弧高度
+        // 顶部左右圆角(半径 15,与高度 30 配合形成上凸圆弧)
+        borderRadius: [15, 15, 0, 0], 
+        fill: '#FFFFFF', // 白色填充
+        z: -1          // 层级低于图表元素(如柱子、坐标轴)
+      }
     ]
   };
 
@@ -159,9 +180,9 @@ const updateChart = () => {
 }
 
 // 生命周期管理
-onMounted(() => {
-  initData();
-  initChart();
+onMounted(async () => {
+  await initData(); // 先加载接口数据
+  initChart();      // 再初始化图表
   // 监听窗口 resize,自动调整图表大小
   window.addEventListener('resize', () => chart && chart.resize());
 })
@@ -174,10 +195,18 @@ onBeforeUnmount(() => {
 
 <style>
 .chart-page {
+  width: 100%;
+  margin: 0 auto 24px;
+  background-color: white;
+  border-radius: 12px;
   padding: 20px;
+  box-sizing: border-box;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
 }
 .chart-container {
   width: 100%;
-  height: 500px; /* 确保图表有足够高度 */
+  height: 500px; 
+  
 }
+
 </style>

+ 85 - 13
src/views/User/HmOutFlux/irrigationWater/crossSetionTencentmap.vue → src/components/irrpollution/crossSetionTencentmap.vue

@@ -219,25 +219,96 @@ onBeforeUnmount(() => {
   border-radius: 12px;
 }
 
-/* 信息窗口核心调整:暴力放大 + 宽高适配 */
+/* 表格容器 */
+.table-page { 
+  width: 100%; 
+  background-color: rgba(255, 255, 255, 0.85);
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  transition: transform 0.3s ease;
+}
+
+.table-page:hover {
+  transform: translateY(-3px);
+}
+
+.table-container {
+  padding: 20px;
+  flex: 1;
+  overflow: auto;
+}
+
+.table-title {
+  text-align: left;
+  margin: 0;
+  padding: 15px 15px 15px 24px;
+  position: relative;
+  font-size: 1.5rem;
+  font-weight: 600;
+  color: #1e88e5;
+  line-height: 1.2;
+  background-color: rgba(30, 136, 229, 0.08);
+  border-bottom: 1px solid rgba(30, 136, 229, 0.15);
+}
+
+/* 蓝色小方块 */
+.table-title::before {
+  content: "";
+  position: absolute;
+  left: 8px;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 8px;
+  height: 8px;
+  background-color: #1e88e5;
+  border-radius: 50%;
+}
+
+.data-table {
+  width: 100%;
+  border-collapse: collapse;
+  min-width: 800px;
+  background-color: rgba(255, 255, 255, 0.7);
+}
+
+.data-table th,.data-table td {
+  padding: 12px 15px;
+  text-align: center;
+  border: 1px solid rgba(229, 231, 235, 0.7);
+}
+
+.data-table th {
+  background-color: rgba(243, 244, 246, 0.7);
+  font-weight: bold;
+  color: #1f2937;
+}
+
+.data-table tr:nth-child(even){
+  background-color: rgba(249, 250, 251, 0.7);
+}
+
+.data-table tr:hover{
+  background-color: rgba(243, 244, 246, 0.7);
+}
+
+/* 信息窗口核心调整 */
 :v-deep(.tmap-infowindow) {
-  padding: 20px !important;  /* 超大内边距 */
-  min-width: 320px !important; /* 强制加宽 */
-  font-size: 1.25rem !important; /* 基准字体爆炸大 */
-  box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important; /* 阴影增强 */
+  padding: 20px !important;
+  min-width: 320px !important;
+  font-size: 1.25rem !important;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.2) !important;
+  border-radius: 8px !important;
+  background: rgba(255, 255, 255, 0.95) !important;
+  backdrop-filter: blur(4px);
 }
 
 .water-info-window {
-  background-color: #FFFFFF;
-  border-radius: 8px;
-  box-shadow: 0 6px 20px rgba(0, 32, 71, 0.15);
   font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-  transition: transform 0.3s ease;
-  border: 1px solid #e0e7ef;
 }
 
 .info-title {
-  background: linear-gradient(135deg, #1e88e5, #1565c0);
+  background: linear-gradient(135deg, rgba(30, 136, 229, 0.85), rgba(21, 101, 192, 0.85));
   color: white;
   font-size: 1.15rem;
   font-weight: 600;
@@ -245,7 +316,7 @@ onBeforeUnmount(() => {
   margin: 0;
   position: relative;
   letter-spacing: 0.5px;
-  border-bottom: 1px solid #e0e7ef;
+  border-radius: 6px 6px 0 0;
 }
 
 .info-title:after {
@@ -263,6 +334,7 @@ onBeforeUnmount(() => {
   margin-bottom: 15px;
   align-items: center;
   position: relative;
+  padding-left: 20px;
 }
 
 .info-row:last-child {
@@ -307,7 +379,7 @@ onBeforeUnmount(() => {
   flex: 1;
   color: #263238;
   font-size: 1rem;
-  background: #f8f9fa;
+  background: rgba(248, 249, 250, 0.7);
   padding: 10px 15px;
   border-radius: 6px;
   border-left: 3px solid #64b5f6;

+ 84 - 56
src/views/User/HmOutFlux/atmosDeposition/atmcompanymap.vue → src/components/irrpollution/crosssectionmap.vue

@@ -22,18 +22,19 @@ const blueTriangle = L.divIcon({
 });
 
 onMounted(() => {
+  // 初始化地图(保持不变)
   if (!mapContainer.value) {
     console.error('❌ 地图容器未找到!');
     return;
   }
 
   const map = L.map(mapContainer.value, {
-    center: [24.7, 114], // 韶关大致中心
+    center: [24.9, 114], // 韶关大致中心  前大往下,后大往左
     zoom: 8.5,
     minZoom: 8.3,
   });
 
-  // 区县颜色映射(保持不变)
+  // 区县颜色映射 + 增强匹配(保持不变)
   const districtColorMap = {
     "武江区": "#FF6B6B",
     "浈江区": "#4ECDC4",
@@ -48,7 +49,6 @@ onMounted(() => {
     "韶关市": "#cccccc",
   };
 
-  // 区县颜色匹配函数(保持不变)
   function getDistrictColor(name) {
     if (districtColorMap[name]) return districtColorMap[name];
     const normalizedName = name.replace(/市|县|区|自治县/g, '');
@@ -68,96 +68,128 @@ onMounted(() => {
     })
     .then(geojson => {
       L.geoJSON(geojson, {
-        style: (feature) => ({
-          fillColor: getDistrictColor(feature.properties.name || ''),
-          fillOpacity: 0.7,
-          color: '#333333',
-          weight: 2,
-        }),
+        style: (feature) => {
+          const districtName = feature.properties.name || '';
+          const color = getDistrictColor(districtName);
+          return {
+            fillColor: color,
+            fillOpacity: 0.7,
+            color: '#333333',
+            weight: 2,
+          };
+        },
       }).addTo(map);
 
+      // 加载水系图 + 新增接口数据加载(核心修改)
+      fetch('/data/韶关市河流水系图.geojson')
+        .then(res => {
+          if (!res.ok) throw new Error(`水系图加载失败:${res.status}`);
+          return res.json();
+        })
+        .then(waterGeojson => {
+          L.geoJSON(waterGeojson, {
+            style: {
+              color: '#0066CC',
+              weight: 2,
+              opacity: 0.8,
+            },
+          }).addTo(map);
 
-    // 从接口加载大气污染源数据(核心修改:适配新接口)
-      fetch('http://localhost:3000/table/Atmosphere_company_data')
-             .then(res => {
-            if (!res.ok) throw new Error(`接口请求失败:${res.status}`);
-            return res.json();
+          // ========================
+          // 从接口加载数据(替换本地rawData)
+          // ========================
+          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(apiData => {
-              console.log('✅ 接口数据加载完成,共', apiData.length, '条记录');
-              
+            .then(geoJSONData => {
+              // 提取GeoJSON的features.properties作为数据项
+              const dataItems = geoJSONData.features.map(feature => feature.properties);
+              console.log('✅ 接口数据加载完成,要素数:', dataItems.length);
+
               let markerCount = 0;
-              apiData.forEach((item, idx) => {
+              dataItems.forEach((item, idx) => {
                 try {
-                  // 解析经纬度(注意:接口里经纬度是字符串,需转数字)
-                  const lng = parseFloat(item.经度);
-                  const lat = parseFloat(item.纬度);
+                  // 字段映射(接口字段 → 原逻辑字段)
+                  const mappedItem = {
+                    "断面编号": item.id,
+                    "所属河流": item.river_name,
+                    "断面位置": item.position,
+                    "所属区县": item.county,
+                    "经度": item.longitude,
+                    "纬度": item.latitude,
+                    "Cd(ug/L)": item.cd_concentration
+                  };
+
+                  // 经纬度校验(保持原有逻辑)
+                  const lng = parseFloat(mappedItem.经度);
+                  const lat = parseFloat(mappedItem.纬度);
                   if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
-                    console.warn(`❌ 无效坐标(第${idx}条):`, lat, lng);
+                    console.warn(`❌ 坐标越界(第${idx}条):`, lat, lng, mappedItem);
                     return;
                   }
 
-                  // 解析大气颗粒物排放量
-                  const emission = parseFloat(item["大气颗粒物排放(t/a)"]);
-                  const formattedEmission = isNaN(emission) 
-                    ? '未知' 
-                    : `${emission.toFixed(3)} t/a`;
-
-                  // 创建标记(保持不变)
+                  // 创建标记(保持原有样式)
                   const marker = L.marker([lat, lng], {
                     icon: blueTriangle,
                     zIndexOffset: 1000,
                   }).addTo(map);
 
-                  // 弹窗内容修改:适配新字段
+                  // 镉含量格式化(保持原有逻辑)
+                  const cdValue = parseFloat(mappedItem["Cd(ug/L)"]);
+                  const formattedCd = isNaN(cdValue) ? '未知' : cdValue.toFixed(2) + ' μg/L';
+
+                  // 弹窗内容(保持原有结构)
                   marker.bindPopup(`
                     <div class="popup-container">
-                      <h3 class="popup-title">${item.公司 || '未知'}</h3>
+                      <h3 class="popup-title">所属河流: ${mappedItem.所属河流}</h3>
                       <div class="popup-divider"></div>
-                      <p><strong>污染源序号:</strong> ${item["污染源序号"] || '未知'}</p>
-                      <p><strong>企业类型:</strong> ${item.类型 || '未知'}</p>
-                      <p><strong>所属区县:</strong> ${item["所属区县"] || '未知'}</p>
-                      <p><strong>大气颗粒物排放:</strong> ${formattedEmission}</p>
+                      <p><strong>断面编号:</strong> ${mappedItem.断面编号}</p>
+                      <p><strong>断面位置:</strong> ${mappedItem.断面位置}</p>
+                      <p><strong>所属区县:</strong> ${mappedItem.所属区县}</p>
+                      <p><strong>镉(Cd)含量:</strong> ${formattedCd}</p>
                     </div>
                   `);
 
+                  // 鼠标交互(保持原有逻辑)
+                  marker.on('mouseover', () => {
+                    marker.getElement().querySelector('svg').style.transform = 'scale(1.2)';
+                  }).on('mouseout', () => {
+                    marker.getElement().querySelector('svg').style.transform = 'scale(1)';
+                  });
+
                   markerCount++;
                 } catch (err) {
                   console.error(`❌ 处理第${idx}条数据失败:`, err);
                 }
               });
 
-              console.log(`✅ 成功创建 ${markerCount} 个有效标记`);
+              console.log(`✅ 成功创建 ${markerCount} 个标记`);
             })
             .catch(err => {
-              console.error('❌ 接口数据加载失败:', err);
-              alert('数据加载错误:' + err.message);
+              console.error('❌ 采样点数据加载失败:', err);
+              alert('采样点数据加载错误:' + err.message);
             });
+          // ========================
         })
+        .catch(err => {
+          console.error('❌ 水系图加载失败:', err);
+          alert('水系图加载错误:' + err.message);
+        });
+    })
     .catch(err => {
       console.error('❌ 区县边界加载失败:', err);
       alert('区县边界加载错误:' + err.message);
     });
-
-    map.on('load', () => {
-    // 延迟执行,确保 DOM 已完全渲染
-    setTimeout(() => {
-      map.invalidateSize(); // 强制 Leaflet 重新计算地图尺寸
-      console.log('✅ 地图尺寸已重新计算');
-    }, 300);
-  });
-
-  // 监听窗口大小变化,确保响应式布局中地图尺寸正确
-  window.addEventListener('resize', () => {
-    map.invalidateSize();
-  });
 });
 </script>
 
 <style scoped>
+/* 原有样式保持不变 */
 .map-wrapper {
   width: 100%;
-  height: 100%;
+  height: 80%;
   position: relative;
 }
 .map-container {
@@ -165,7 +197,6 @@ onMounted(() => {
   height: 100% !important;
 }
 
-/* 弹窗样式 */
 ::v-deep .popup-title {
   text-align: center;
   font-size: 18px;
@@ -206,7 +237,6 @@ onMounted(() => {
   font-weight: bold;
 }
 
-/* 美化弹窗 */
 ::v-deep .leaflet-popup-content-wrapper {
   padding: 0 !important;
   border-radius: 12px !important;
@@ -222,7 +252,6 @@ onMounted(() => {
   display: none;
 }
 
-/* 图例样式 */
 ::v-deep .info {
   padding: 6px 8px;
   background: white;
@@ -231,7 +260,6 @@ onMounted(() => {
   border-radius: 5px;
 }
 
-/* 自定义标记样式 */
 ::v-deep .custom-div-icon svg {
   transition: transform 0.2s;
   display: block;

+ 282 - 0
src/components/irrpollution/irrwatermap.vue

@@ -0,0 +1,282 @@
+<template>
+  <div class="map-wrapper" @click.stop>
+    <div ref="mapContainer" class="map-container"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue';
+import L from 'leaflet';
+import 'leaflet/dist/leaflet.css';
+
+const mapContainer = ref(null);
+
+onMounted(() => {
+  // 初始化地图(强制确保容器可用)
+  if (!mapContainer.value) {
+    console.error('❌ 地图容器未找到!');
+    return;
+  }
+
+  // 定义位置格式化函数(处理"广东省韶关市"前缀)
+  const formatLocation = (fullLocation) => {
+    if (!fullLocation) return '未知位置'; // 处理空值
+    // 移除前缀并清理空格
+    const processed = fullLocation.replace(/^广东省韶关市/, '').trim();
+    // 处理移除后为空的情况
+    return processed || '未知位置';
+  };
+
+  const map = L.map(mapContainer.value, {
+    center: [25, 114], // 韶关大致中心  前大往下,后大往左
+    zoom: 8.5,
+    minZoom: 8.3,
+  });
+
+  // 区县颜色映射(与GeoJSON的properties.name严格匹配)
+  const districtColorMap = {
+    "武江区": "#FF6B6B",
+    "浈江区": "#4ECDC4",
+    "曲江区": "#FFD166",
+    "始兴县": "#A0DAA9",
+    "仁化县": "#6A0572",
+    "翁源县": "#1A535C",
+    "乳源瑶族自治县": "#FF9F1C",
+    "新丰县": "#87CEEB",
+    "乐昌市": "#118AB2",
+    "南雄市": "#06D6A0",
+  };
+
+  // 加载区县边界(带完整错误处理)
+  fetch('/data/韶关市各区县边界图.geojson')
+    .then(res => {
+      if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
+      return res.json();
+    })
+    .then(geojson => {
+      console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
+      
+      L.geoJSON(geojson, {
+        style: (feature) => {
+          const districtName = feature.properties.name; // 匹配GeoJSON的name字段
+          const color = districtColorMap[districtName] || '#cccccc';
+          return {
+            fillColor: color,
+            fillOpacity: 0.7,
+            color: '#333333', // 边界颜色
+            weight: 2,        // 边界宽度
+          };
+        },
+      }).addTo(map);
+
+      // 加载水系图(新增,带样式和错误处理)
+      fetch('/data/韶关市河流水系图.geojson')
+        .then(res => {
+          if (!res.ok) throw new Error(`水系图加载失败:${res.status}`);
+          return res.json();
+        })
+        .then(waterGeojson => {
+          console.log('✅ 水系图数据加载完成,要素数:', waterGeojson.features.length);
+          
+          L.geoJSON(waterGeojson, {
+            style: {
+              color: '#0066CC', // 水系颜色
+              weight: 2,       // 线条宽度
+              opacity: 0.8,    // 透明度
+            },
+          }).addTo(map);
+
+          // ========================
+          // 修复核心:加载采样+检测数据(单接口)
+          // ========================
+          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 结构
+              console.log('✅ 采样数据加载完成,要素数:', geoJSONData.features.length);
+                
+              let markerCount = 0;
+              geoJSONData.features.forEach((feature, idx) => {
+                const props = feature.properties; // 单个要素的完整属性(采样+检测)
+                try {
+                  // 智能提取经纬度字段(支持多种可能的字段名)
+                  const latField = ['latitude', 'lat', 'Latitude', 'Lat'].find(key => props[key] !== undefined);
+                  const lngField = ['longitude', 'lng', 'Longitude', 'Lng'].find(key => props[key] !== undefined);
+                  
+                  if (!latField || !lngField) {
+                    console.error(`❌ 未找到经纬度字段(第${idx}条):`, props);
+                    return;
+                  }
+                  
+                  // 清理并转换经纬度(处理特殊字符和逗号)
+                  const cleanLat = String(props[latField]).replace(/[^\d.-]/g, '');
+                  const cleanLng = String(props[lngField]).replace(/[^\d.-]/g, '');
+                  
+                  // 强制四舍五入到6位小数(避免精度问题)
+                  const lat = parseFloat(parseFloat(cleanLat).toFixed(6));
+                  const lng = parseFloat(parseFloat(cleanLng).toFixed(6));
+                  
+                  // 范围校验(扩大范围10%,兼容边界值)
+                  if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
+                    console.warn(`❌ 坐标超出合理范围(第${idx}条):`, lat, lng, props);
+                    return;
+                  }
+                  
+                  // 创建标记点(使用 L.circleMarker 而非 L.marker)
+                  const marker = L.circleMarker([lat, lng], {
+                    radius: 4,                  // 增大圆点半径,确保可见
+                    color: '#FF3333',           // 边框颜色(红色)
+                    fillColor: '#FF3333',       // 填充颜色(红色)
+                    fillOpacity: 0.9,           // 填充透明度(接近不透明)
+                    weight: 2,                  // 边框宽度(加粗)
+                    zIndexOffset: 1000,         // 提高层级,确保在所有图层之上
+                  }).addTo(map);
+
+                  // 弹窗内容:直接从 props 提取检测数据(匹配实际字段名)
+                  marker.bindPopup(`
+                    <div class="popup-container">
+                      <h3 class="popup-title">${formatLocation(props.sampling_location)}</h3>
+                      <div class="popup-divider"></div> <!-- 分隔线 -->
+                      <table class="popup-table">
+                        <thead>
+                          <tr>
+                            <th>检测项</th>
+                            <th>数值</th>
+                          </tr>
+                        </thead>
+                        <tbody>
+                          <tr><td>Ph</td><td>${props.ph_value || '未知'}</td></tr>
+                          <tr><td>铬(Cr)</td><td>${props.cr_concentration || '未知'}</td></tr>
+                          <tr><td>砷(As)</td><td>${props.as_concentration || '未知'}</td></tr>
+                          <tr><td>镉(Cd)</td><td>${props.cd_concentration || '未知'}</td></tr>
+                          <tr><td>汞(Hg)</td><td>${props.hg_concentration || '未知'}</td></tr>
+                          <tr><td>铅(Pb)</td><td>${props.pb_concentration || '未知'}</td></tr>
+                        </tbody>
+                      </table>
+                    </div>
+                  `);
+                  
+                  markerCount++;
+                } catch (err) {
+                  console.error(`❌ 处理采样点失败(第${idx}条):`, err);
+                }
+              });
+
+              console.log(`✅ 成功创建 ${markerCount} 个标记点`);
+            })
+            .catch(err => {
+              console.error('❌ 采样/检测数据加载失败:', err);
+              alert('数据接口错误:' + err.message);
+            });
+          // ========================
+          // 水系图加载完成后的逻辑结束
+          // ========================
+        })
+        .catch(err => {
+          console.error('❌ 水系图加载失败:', err);
+          alert('水系图加载错误:' + err.message);
+        });
+    })
+    .catch(err => {
+      console.error('❌ 区县边界加载失败:', err);
+      alert('区县边界加载错误:' + err.message);
+    });
+});
+</script>
+
+<style scoped>
+.map-wrapper {
+  width:100%;
+  height: 100%;
+  position: relative;
+  z-index: 100;
+}
+.map-container {
+  width: 100% !important;
+  height: 500px !important;
+}
+
+/*  标题和分隔线 */
+::v-deep .popup-title {
+  text-align: center;       /* 居中 */
+  font-size: 16px;          /* 减小字号 */
+  font-weight: 700;         /* 加粗 */
+  color: #0066CC;           /* 蓝色,匹配设计 */
+  margin: 0 0 4px;          /* 间距调整 */
+  border-bottom: 2px solid #0066CC; /* 底部横线 */
+  padding-bottom: 4px;      /* 横线与文字间距 */
+}
+
+::v-deep .popup-divider {
+  height: 1px;              /* 横线高度 */
+  background: #0066CC;      /* 横线颜色 */
+  margin: 6px 0;            /* 上下间距 */
+}
+
+/*  表格样式 */
+::v-deep .popup-table {
+  width: 100%;              /* 占满容器 */
+  border-collapse: collapse;/* 合并边框 */
+  margin-top: 12px;         /* 与段落间距 */
+}
+
+::v-deep .popup-table th,
+::v-deep .popup-table td {
+  border: 1px solid #CCCCCC;/* 单元格边框 */
+  padding: 4px 6px;         /* 内边距 */
+  text-align: center;       /* 内容居中 */
+  font-size: 12px;          /* 字号调整 */
+}
+
+::v-deep .popup-table th {
+  background: #F5F5F5;      /* 表头背景色 */
+  font-weight: 600;         /* 表头加粗 */
+}
+
+/* 美化弹窗(完整层级穿透) */
+::v-deep .leaflet-popup-content-wrapper {
+  padding: 0 !important;
+  border-radius: 12px !important;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
+}
+
+::v-deep .leaflet-popup-content {
+  margin: 0 !important;
+  width: auto !important;
+  max-width: 220px !important;
+}
+
+::v-deep .popup-container {
+  min-width: 180px;
+  max-width: 220px;
+  padding: 10px;/**内边距 */
+  font-family: "Microsoft YaHei", sans-serif;
+}
+
+::v-deep .popup-content p {
+  margin: 6px 0;
+  font-size: 15px;
+  color: #666;
+  line-height: 1.6;
+}
+
+::v-deep .popup-content strong {
+  color: #FF3333; /* 与标记点颜色呼应 */
+  font-weight: 600;
+}
+
+/* 可选:隐藏弹窗箭头,更像卡片 */
+::v-deep .leaflet-popup-tip {
+  display: none;
+}
+
+/* 临时调试:确保标记点可见 */
+::v-deep .leaflet-marker-icon {
+  display: none !important; /* 隐藏默认标记图标 */
+}
+::v-deep .leaflet-circle-marker {
+  stroke-width: 2px !important;
+}
+</style>

+ 0 - 0
src/views/User/HmOutFlux/irrigationWater/riverwaterassay.vue → src/components/irrpollution/riverwaterassay.vue


+ 0 - 0
src/views/User/HmOutFlux/irrigationWater/tencentMapView.vue → src/components/irrpollution/tencentMapView.vue


+ 0 - 0
src/views/User/HmOutFlux/irrigationWater/waterassaydata1.vue → src/components/irrpollution/waterassaydata1.vue


+ 66 - 53
src/views/User/HmOutFlux/irrigationWater/waterassaydata2.vue → src/components/irrpollution/waterassaydata2.vue

@@ -11,18 +11,19 @@ import { ref, onMounted, onUnmounted } from 'vue';
 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 NEW_API = 'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data';
 
-// ========== 配置项 ==========
+// ========== 配置项(调整字段适配新接口) ==========
+// 排除的非重金属字段(适配新接口字段名)
 const EXCLUDE_FIELDS = [
-  'water_assay_ID', 'sample_code', 'assayer_ID', 'assay_time', 
-  'assay_instrument_model', 'water_sample_ID', 'pH'
+  'id', 'sample_code', 'assayer_id', 'assay_time', 
+  'assay_instrument_model', 'sample_number', 'ph_value',
+  'latitude', 'longitude', 'sampling_location', 'sampling_time' // 新增采样相关非检测字段
 ];
 const COLORS = ['#ff4d4f99', '#1890ff', '#ffd700',  '#52c41a88', '#722ed199' ];
 
-// 韶关市下属行政区划白名单 [关键修改点]
+// 韶关市下属行政区划白名单(保持不变)
 const SG_REGIONS = [
   '浈江区', '武江区', '曲江区', '乐昌市', 
   '南雄市', '始兴县', '仁化县', '翁源县', 
@@ -35,7 +36,7 @@ const loading = ref(true);
 const error = ref('');
 let myChart = null;
 
-// ========== 地区提取函数(韶关市专属版)[核心修改] ==========
+// ========== 地区提取函数(保持不变) ==========
 const extractRegion = (location) => {
   if (!location || typeof location !== 'string') return null;
 
@@ -49,7 +50,6 @@ const extractRegion = (location) => {
   const nestedMatch = location.match(/(韶关市)([^市]+?[区市县])/);
   if (nestedMatch && nestedMatch[2]) {
     const region = nestedMatch[2].replace("韶关市", "").trim();
-    // 验证是否为合法区县
     const validRegion = SG_REGIONS.find(r => r.includes(region));
     if (validRegion) return validRegion;
   }
@@ -66,45 +66,49 @@ const extractRegion = (location) => {
   return '未知区县';
 };
 
-// ========== 数据处理流程 ==========
-const processMergedData = (samplingData, assayData) => {
-  // 1. 构建采样点ID到区县的映射
+// ========== 数据处理流程(适配新接口单数据源) ==========
+const processData = (allData) => {
+  // 1. 构建采样点ID到区县的映射(sample_number对应新接口的水样ID)
   const regionMap = new Map();
-  samplingData.forEach(item => {
+  allData.forEach(item => {
     const region = extractRegion(item.sampling_location || '');
-    if (region && region !== '未知区县') {
-      regionMap.set(item.water_sample_ID, region);
+    if (region && region !== '未知区县' && item.sample_number) {
+      regionMap.set(item.sample_number, region);
     }
   });
 
-  // 2. 关联重金属数据与区县
-  const mergedData = assayData.map(item => ({
+  // 2. 关联重金属数据与区县(单条数据已包含所有信息)
+  const mergedData = allData.map(item => ({
     ...item,
-    region: regionMap.get(item.water_sample_ID) || '未知区县'
+    // 通过sample_number关联区县
+    region: regionMap.get(item.sample_number) || '未知区县'
   }));
 
-  // 3. 识别重金属字段
+  // 3. 识别重金属字段(新接口字段如cr_concentration、as_concentration等)
   const metals = Object.keys(mergedData[0] || {})
-    .filter(key => !EXCLUDE_FIELDS.includes(key) && !isNaN(parseFloat(mergedData[0][key])));
+    .filter(key => 
+      !EXCLUDE_FIELDS.includes(key) &&  // 排除非重金属字段
+      !isNaN(parseFloat(mergedData[0][key])) &&  // 确保是数值
+      key.includes('concentration')  // 新接口重金属字段含concentration
+    );
 
   // 4. 按区县分组统计
   const regionGroups = {};
-  const cityWideAverages = {}; // 新增:全市平均值
+  const cityWideAverages = {}; // 全市平均值
   const uniqueSampleIds = new Set();
   
-  // 初始化全市平均值计数器
+  // 初始化统计计数器
   metals.forEach(metal => {
     cityWideAverages[metal] = { sum: 0, count: 0 };
   });
   
   mergedData.forEach(item => {
     const region = item.region;
-
-    if(item.water_sample_ID){
-      uniqueSampleIds.add(item.water_sample_ID);
+    if (item.sample_number) {
+      uniqueSampleIds.add(item.sample_number);
     }
 
-    // 区县统计
+    // 初始化区县分组
     if (!regionGroups[region]) {
       regionGroups[region] = {};
       metals.forEach(metal => {
@@ -112,14 +116,15 @@ const processMergedData = (samplingData, assayData) => {
       });
     }
 
+    // 统计各重金属含量
     metals.forEach(metal => {
       const val = parseFloat(item[metal]);
       if (!isNaN(val)) {
-        // 更新区县统计
+        // 区县统计
         regionGroups[region][metal].sum += val;
         regionGroups[region][metal].count++;
         
-        // 更新全市统计
+        // 全市统计
         cityWideAverages[metal].sum += val;
         cityWideAverages[metal].count++;
       }
@@ -134,15 +139,21 @@ const processMergedData = (samplingData, assayData) => {
   // 6. 添加"全市平均"作为最后一个类别
   regions.push("全市平均");
 
-  // 7. 构建ECharts数据(包括全市平均值
+  // 7. 构建ECharts数据(处理重金属字段名显示
   const series = metals.map((metal, idx) => {
+    // 格式化重金属名称(如cr_concentration → Cr)
+    const prefix = metal.split('_')[0]; // 先获取前缀
+    const metalName = prefix 
+    ? prefix[0].toUpperCase() + prefix.slice(1) // 首字母大写 + 剩余字符
+    : ''; // 处理空字符串情况 
+    
     // 计算全市平均值
     const cityWideAvg = cityWideAverages[metal].count 
       ? (cityWideAverages[metal].sum / cityWideAverages[metal].count).toFixed(2) 
       : 0;
     
     return {
-      name: metal,
+      name: metalName, // 显示简化名称(如Cr、As)
       type: 'bar',
       data: regions.map(region => {
         if (region === "全市平均") {
@@ -157,8 +168,8 @@ const processMergedData = (samplingData, assayData) => {
       label: {
         show: true,
         position: 'top',
-        fontSize:18,
-        color:'#333',
+        fontSize: 15,
+        color: '#333',
       }
     };
   });
@@ -166,7 +177,7 @@ const processMergedData = (samplingData, assayData) => {
   return { regions, series, totalSamples };
 };
 
-// ========== ECharts 初始化 ==========
+// ========== ECharts 初始化(保持不变) ==========
 const initChart = ({ regions, series, totalSamples }) => {
   if (!chartRef.value) return;
   if (myChart) myChart.dispose();
@@ -176,8 +187,8 @@ const initChart = ({ regions, series, totalSamples }) => {
     title: { 
       left: 'center',
       subtext: `数据来源: ${totalSamples}个有效检测样本`,
-      subtextStyle:{
-        fontSize:18  //提示框字体大小
+      subtextStyle: {
+        fontSize: 15
       }
     },
     tooltip: { 
@@ -193,8 +204,8 @@ const initChart = ({ regions, series, totalSamples }) => {
         
         return content + params.map(p => `<br>${p.seriesName}: ${p.value} ug/L`).join('');
       },
-      textSize:{
-        fontSize:20
+      textStyle: {
+        fontSize: 15
       }
     },
     xAxis: {
@@ -203,17 +214,17 @@ const initChart = ({ regions, series, totalSamples }) => {
       axisLabel: { 
         rotate: 45,
         formatter: val => val.replace('韶关市', ''),
-        fontSize:20
+        fontSize: 15
       }
     },
     yAxis: { 
       type: 'value', 
-      name: '浓度(ug/L)' ,
-      nameTextStyle:{
-        fontSize:20,
+      name: '浓度(ug/L)',
+      nameTextStyle: {
+        fontSize: 15,
       },
-      axisLabel:{
-        fontSize:20,
+      axisLabel: {
+        fontSize: 15,
       }
     },
     dataZoom: [{
@@ -225,8 +236,8 @@ const initChart = ({ regions, series, totalSamples }) => {
     legend: { 
       data: series.map(s => s.name), 
       bottom: 10,
-      textStyle:{
-        fontSize:18
+      textStyle: {
+        fontSize: 15
       }
     },
     grid: { 
@@ -240,15 +251,16 @@ const initChart = ({ regions, series, totalSamples }) => {
   myChart.setOption(option);
 };
 
-// ========== 生命周期钩子 ==========
+// ========== 生命周期钩子(适配新接口) ==========
 onMounted(async () => {
   try {
-    const [samplingRes, assayRes] = await Promise.all([
-      axios.get(SAMPLING_API, { timeout: 10000 }),
-      axios.get(ASSAY_API, { timeout: 10000 })
-    ]);
+    // 仅请求新接口
+    const response = await axios.get(NEW_API, { timeout: 10000 });
+    // 从GeoJSON中提取数据(features.properties)
+    const allData = response.data.features.map(feature => feature.properties);
     
-    initChart(processMergedData(samplingRes.data, assayRes.data));
+    // 处理数据并初始化图表
+    initChart(processData(allData));
   } catch (err) {
     error.value = '数据加载失败: ' + (err.message || '未知错误');
     console.error('接口错误:', err);
@@ -257,7 +269,7 @@ onMounted(async () => {
   }
 });
 
-// 响应式布局
+// 响应式布局(保持不变)
 const resizeHandler = () => myChart && myChart.resize();
 onMounted(() => window.addEventListener('resize', resizeHandler));
 onUnmounted(() => window.removeEventListener('resize', resizeHandler));
@@ -266,13 +278,14 @@ onUnmounted(() => window.removeEventListener('resize', resizeHandler));
 <style scoped>
 .region-average-chart {
   width: 100%;
+  height: 500px;
   max-width: 1200px;
   margin: 0 auto;
   position: relative;
 }
 .chart-box {
   width: 100%;
-  height: 600px;
+  height: 500px;
   min-height: 400px;
   background-color: white;
   border-radius: 8px;

+ 0 - 0
src/views/User/HmOutFlux/irrigationWater/waterassaydata3.vue → src/components/irrpollution/waterassaydata3.vue


+ 0 - 0
src/views/User/HmOutFlux/irrigationWater/waterassaydata4.vue → src/components/irrpollution/waterassaydata4.vue


+ 38 - 38
src/views/User/HmOutFlux/irrigationWater/waterdataline.vue → src/components/irrpollution/waterdataline.vue

@@ -1,10 +1,7 @@
 <template>
   <div class="container mx-auto px-4 py-8">
     <div class="bg-white rounded-xl shadow-lg overflow-hidden">
-      <div class="p-6 border-b border-gray-200">
-        <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-gray-800 text-center">水质检测数据列表</h1>
-        <p class="text-gray-500 text-center mt-2">实时监测与分析水质指标</p>
-      </div>
+      
       
       <!-- 加载状态 -->
       <div v-if="loading" class="py-20 flex justify-center items-center">
@@ -81,15 +78,15 @@
         </div>
       </div>
   
-      <!-- 数据表格 + 统计 -->
-  <div class="p-4 bg-gray-50 border-t border-gray-200">
-    <div class="flex flex-col md:flex-row justify-between items-center">
-      <div class="text-sm text-gray-500 mb-2 md:mb-0">
-        共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
+      <!-- 数据统计 -->
+      <div class="p-4 bg-gray-50 border-t border-gray-200">
+        <div class="flex flex-col md:flex-row justify-between items-center">
+          <div class="text-sm text-gray-500 mb-2 md:mb-0">
+            共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
+          </div>
+        </div>
       </div>
     </div>
-</div>
-    </div>
   </div>
 </template>
 
@@ -97,17 +94,16 @@
 import { ref, computed, onMounted } from 'vue';
 import axios from 'axios';
 
-// 定义固定列配置
+// 适配接口的字段映射(关键修改点)
 const displayColumns = ref([
-  { key: 'water_assay_ID', label: '检测ID' },
-  { key: 'pH', label: 'PH值' },
-  { key: 'Cr', label: '铬含量(ug/L)' },
-  { key: 'As', label: '砷含量(ug/L)' },
-  { key: 'Cd', label: '镉含量(ug/L)' },
-  { key: 'Hg', label: '汞含量(ug/L)' },
-  { key: 'Pb', label: '铅含量(ug/L)' },
-  { key: 'sample_code', label: '样本编码' },
-  { key: 'water_sample_ID', label: '水样ID' },
+  { key: 'sample_number', label: '水样ID' }, // 原water_sample_ID对应接口的sample_number
+  { key:'sampling_location',label:'地理位置'},
+  { key: 'ph_value', label: 'PH值' },
+  { key: 'cr_concentration', label: '铬含量(ug/L)' },
+  { key: 'as_concentration', label: '砷含量(ug/L)' },
+  { key: 'cd_concentration', label: '镉含量(ug/L)' },
+  { key: 'hg_concentration', label: '汞含量(ug/L)' },
+  { key: 'pb_concentration', label: '铅含量(ug/L)' },
 ]);
 
 // 状态管理
@@ -117,13 +113,16 @@ const error = ref(null);
 const sortKey = ref('');
 const sortOrder = ref('asc');
 
-// 获取数据
+// 接口请求(关键修改点:URL和数据解析)
 const fetchData = async () => {
   try {
     loading.value = true;
     error.value = null;
-    const response = await axios.get('http://localhost:3000/table/Water_assay_data');
-    waterData.value = response.data.data || response.data;
+    const response = await axios.get(
+      'http://localhost:8000/api/vector/export/all?table_name=water_sampling_data'
+    );
+    // 解析GeoJSON数据:提取features中的properties作为数据项
+    waterData.value = response.data.features.map(feature => feature.properties);
   } catch (err) {
     error.value = err.message || '无法连接到服务器,请检查接口是否可用';
   } finally {
@@ -131,14 +130,14 @@ const fetchData = async () => {
   }
 };
 
-// 过滤全空行
+// 过滤全空行(逻辑保留,自动适配新字段)
 const filteredData = computed(() => {
   return waterData.value.filter(item => {
     return displayColumns.value.some(col => item[col.key] !== null && item[col.key] !== '-');
   });
 });
 
-// 排序功能
+// 排序功能(逻辑保留,自动适配新字段)
 const sortedData = computed(() => {
   if (!sortKey.value) return filteredData.value;
   
@@ -151,7 +150,7 @@ const sortedData = computed(() => {
   });
 });
 
-// 切换排序
+// 切换排序(逻辑保留)
 const sortData = (key) => {
   if (sortKey.value === key) {
     sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
@@ -161,7 +160,7 @@ const sortData = (key) => {
   }
 };
 
-// 组件挂载
+// 组件挂载(逻辑保留)
 onMounted(() => {
   fetchData();
 });
@@ -186,7 +185,7 @@ onMounted(() => {
 
 /* 文字 */
 .text-center { text-align: center; }
-.text-lg { font-size: 1.125rem; }
+.text-lg { font-size: 1rem; }
 .font-bold { font-weight: 700; }
 .text-gray-800 { color: #111827; }
 
@@ -200,7 +199,16 @@ onMounted(() => {
 }
 
 /* 表格 */
-table { width: 100%; }
+table { 
+  width: 100%; 
+  border-collapse: collapse; /* 合并边框线 */
+}
+th, td {
+  border: 1px solid #d1d5db; /* 灰色边框 */
+  text-align: center; /* 内容居中 */
+  padding: 12px 8px; /* 内边距优化 */
+  font-size: 14px;
+}
 .px-6 { padding: 0 1.5rem; }
 .py-4 { padding: 1rem 0; }
 .hover\:bg-gray-50:hover { background-color: #f9fafb; }
@@ -210,12 +218,4 @@ table { width: 100%; }
   .container { padding: 32px 8px; }
   .px-6 { padding: 0 0.75rem; }
 }
-table {
-  border-collapse: collapse; /* 合并边框线 */
-}
-th, td {
-  border: 1px solid #d1d5db; /* 灰色边框 */
-  text-align: center; /* 内容居中 */
-  padding: 12px 8px; /* 内边距优化 */
-}
 </style>

+ 167 - 212
src/components/layout/AppLayout.vue

@@ -1,54 +1,33 @@
 <template>
-  <div class="layout-wrapper" :class="{ 'full-screen': isFullScreen }">
-    <!-- 背景层 - 只在需要透明的地方显示 -->
-    <div 
-      class="background-layer" 
-      v-if="!isFullScreen && !isSelectCity && !isExcludedRoute"
-    ></div>
-    
-    <!-- Header 部分 -->
-    <el-header 
-      class="layout-header" 
-      v-if="!isFullScreen"
-      :class="{ 'excluded-bg-header': isExcludedRoute }"
-    >
+  <div class="layout-wrapper" :class="{ 'full-screen': isFullScreen }" :style="isSpecialBg ? backgroundStyle : {}">
+    <!-- 背景层 - 关键修改 -->
+    <div v-if="isSpecialBg" class="background-layer"></div>
+
+    <!-- Header 部分(透明背景) -->
+    <el-header class="layout-header" v-if="!isFullScreen" :class="{ 'transparent-header': isSpecialBg }">
       <div class="logo-title-row">
         <img src="@/assets/logo.png" alt="Logo" class="logo" />
         <div class="title-and-user">
-          <span 
-            class="project-name"
-            :class="{ 'excluded-text': isExcludedRoute }"
-          >
+          <span class="project-name" :class="{ 'light-text': isSpecialBg }">
             区域土壤重金属污染风险评估
           </span>
-          
+
           <!-- 用户信息 - 在select-city页面隐藏 -->
-          <div 
-            class="user-info-row" 
-            v-if="!isSelectCity"
-            :class="{ 'excluded-text': isExcludedRoute }"
-          >
-            <span class="welcome-text">
+          <div class="user-info-row" v-if="!isSelectCity">
+            <span class="welcome-text" :class="{ 'light-text': isSpecialBg }">
               欢迎{{
                 tokenStore.token.loginType === "admin" ? "管理员" : "用户"
               }}登录成功
             </span>
             <el-dropdown>
               <span class="el-dropdown-link">
-                <el-avatar
-                  :size="40"
-                  :class="{ 'excluded-avatar-border': isExcludedRoute }"
-                  src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
-                />
+                <el-avatar :size="40" :class="{ 'light-avatar-border': isSpecialBg }"
+                  src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
               </span>
               <template #dropdown>
                 <el-dropdown-menu>
-                  <el-dropdown-item disabled
-                    >用户名:{{ userInfo.name }}</el-dropdown-item
-                  >
-                  <el-dropdown-item divided @click="handleLogout"
-                    >退出登录</el-dropdown-item
-                  >
+                  <el-dropdown-item disabled>用户名:{{ userInfo.name }}</el-dropdown-item>
+                  <el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
@@ -57,15 +36,9 @@
       </div>
     </el-header>
 
-    <!-- Tab 区域 - 不透明 -->
+    <!-- Tab 区域(不加背景图) -->
     <div class="tabs-row" v-if="!isFullScreen">
-      <el-tabs
-        v-if="showTabs"
-        v-model="activeName"
-        class="demo-tabs"
-        :style="tabStyle"
-        @tab-click="handleClick"
-      >
+      <el-tabs v-if="showTabs" v-model="activeName" class="demo-tabs" :style="tabStyle" @tab-click="handleClick">
         <el-tab-pane v-for="tab in tabs" :key="tab.name" :name="tab.name">
           <template #label>
             <i :class="['tab-icon', tab.icon]"></i>
@@ -81,27 +54,14 @@
 
     <!-- 主体区域 -->
     <el-container class="layout-main-container">
-      <!-- 侧边栏 - 不透明 -->
+      <!-- 侧边栏(不加背景图) -->
       <el-aside v-if="showAside && showTabs" class="layout-aside">
-        <component
-          :is="AsideComponent"
-          :activeTab="activeName"
-          :showTabs="showTabs"
-        />
+        <component :is="AsideComponent" :activeTab="activeName" :showTabs="showTabs" />
       </el-aside>
 
-      <!-- 内容区域 -->
-      <el-main 
-        class="layout-content-wrapper" 
-        :style="mainStyle"
-        :class="{ 'excluded-bg-content': isExcludedRoute }"
-      >
-        <div
-          class="scrollable-content"
-          :style="{ 
-            backgroundImage: currentBgImage,
-          }"
-        >
+      <!-- 内容区域(透明背景) -->
+      <el-main class="layout-content-wrapper" :style="mainStyle">
+        <div class="scrollable-content" :class="{ 'transparent-scroll': isSpecialBg }">
           <div :class="{ 'select-city-container': isSelectCity }">
             <RouterView />
           </div>
@@ -118,43 +78,85 @@ import { useTokenStore } from "@/stores/mytoken";
 import { ElMessageBox, ElMessage } from "element-plus";
 import { logout } from "@/API/users";
 
-// 使用更可靠的图片导入方式
-function getImageUrl(name: string) {
-  try {
-    return new URL(`../../assets/bg/${name}`, import.meta.url).href;
-  } catch (error) {
-    console.error("加载背景图失败:", error);
-    return "";
-  }
-}
-
 const router = useRouter();
 const route = useRoute();
 const tokenStore = useTokenStore();
 const currentBgImage = ref("");
 
+// 定义需要背景图的特殊路由和对应图片文件名
+const bgRouteMap: Record<string, string> = {
+  // 添加灌溉水相关页面的背景图
+  "/samplingMethodDevice1": "irrigation.jpg",
+  "/irriSampleData": "irrigation.jpg",
+  "/csSampleData": "irrigation.jpg",
+  "/irriInputFlux": "irrigation.jpg",
+  // 添加大农产品投入相关页面的背景图
+  "/farmInputSamplingDesc": "agricultural_input.png",
+  "/prodInputFlux": "agricultural_input.png",
+  // 添加大气干湿沉降相关页面的背景图
+  "/AtmosDepositionSamplingDesc": "atmospheric_deposition.png",
+  "/heavyMetalEnterprise": "atmospheric_deposition.png",
+  "/airSampleData": "atmospheric_deposition.png",
+  "/airInputFlux": "atmospheric_deposition.png",
+  //添加籽粒移除相关页面的背景图
+  "/samplingDesc1": "rain_removal.png",
+  "/grainRemovalInputFlux": "rain_removal.png",
+  //添加秸秆移除相关页面的背景图
+  "/samplingDesc2": "straw-removal.png",
+  "/strawRemovalInputFlux": "straw-removal.png",
+  //添加地下渗漏相关页面的背景图
+  "/samplingDesc3": "subsurface-leakage.jpg",
+  "/subsurfaceLeakageInputFlux": "subsurface-leakage.jpg",
+  //添加地表径流相关页面的背景图
+  "/samplingDesc4": "surface-runoff.jpg",
+  "/surfaceRunoffInputFlux": "surface-runoff.jpg",
+};
+
+// 当前是否为特殊背景图页面
+const isSpecialBg = computed(() => Object.keys(bgRouteMap).includes(route.path));
+
+function getBgImageUrl(): string {
+  const bgFile = bgRouteMap[route.path];
+  console.log("根据路径查找背景文件名:", bgFile);
+  if (bgFile) {
+    try {
+      const url = `url(${new URL(`../../assets/bg/${bgFile}`, import.meta.url).href})`;
+      return url;
+    } catch (error) {
+      console.error("加载背景图失败:", error);
+      return "";
+    }
+  }
+  return "";
+}
+
+// 监听路由变化更新背景图,并打印日志
+watch(
+  () => route.path,
+  (newPath) => {
+    currentBgImage.value = getBgImageUrl();
+    console.log(`[背景图更新] 当前路径: ${newPath}, 背景图: ${currentBgImage.value}`);
+  },
+  { immediate: true }
+);
+
+// 背景图样式 - 关键修改
+const backgroundStyle = computed(() => {
+  return {
+    backgroundImage: currentBgImage.value,
+    backgroundSize: 'cover',
+    backgroundPosition: 'center',
+    backgroundRepeat: 'no-repeat',
+    backgroundAttachment: 'fixed'
+  };
+});
+
 // 是否为全屏页面
 const isFullScreen = computed(() => route.meta.fullScreen === true);
 
 // 判断是否是select-city页面
 const isSelectCity = computed(() => route.path === "/select-city");
 
-// 背景图路径映射 - 使用简单路径
-const routeBackgroundMap: Record<string, string> = {
-  "/samplingMethodDevice1": getImageUrl("irrigation.jpg"),
-  "/irriSampleData": getImageUrl("irrigation.jpg"),
-  "/csSampleData": getImageUrl("irrigation.jpg"),
-  "/irriInputFlux": getImageUrl("irrigation.jpg"),
-  "/prodInputFlux": getImageUrl("agricultural_input.png"),
-  "/airSampleData": getImageUrl("atmospheric_deposition.png"),
-  "/airInputFlux": getImageUrl("atmospheric_deposition.png"),
-  "/heavyMetalEnterprise": getImageUrl("atmospheric_deposition.png"),
-  "/grainRemovalInputFlux": getImageUrl("grain_removal.png"),
-  "/strawRemovalInputFlux": getImageUrl("straw_removal.png"),
-  "/subsurfaceLeakageInputFlux": getImageUrl("subsurface_leakage.jpg"),
-  "/surfaceRunoffInputFlux": getImageUrl("surface_runoff.jpg"),
-};
-
 // Tab 配置
 const tabs = computed(() => {
   if (tokenStore.token.loginType === "admin") {
@@ -226,9 +228,10 @@ const tabs = computed(() => {
           "/irriSampleData",
           "/csSampleData",
           "/irriInputFlux",
-          "/samplingDesc2",
+          "/farmInputSamplingDesc",
           "/prodInputFlux",
-          "/samplingDesc3",
+          "/AtmosDepositionSamplingDesc",
+          "/heavyMetalEnterprise",
           "/airSampleData",
           "/airInputFlux",
         ],
@@ -310,7 +313,7 @@ const tabs = computed(() => {
   }
 });
 
-// 计算:当前激活 tab - 保持不变
+// 当前激活 tab
 const activeName = ref(tabs.value[0]?.name || "");
 const showTabs = computed(() => tabs.value.length > 1);
 const tabStyle = computed(() =>
@@ -334,43 +337,6 @@ watch(
   { immediate: true }
 );
 
-// 显式列出所有不需要背景图的路由路径
-const bgExcludedRoutes = computed(() => {
-  return [
-    // 列出所有不需要背景图的路由路径
-    "/shuJuKanBan",
-    "/mapView",
-    "/cropRiskAssessment",
-    "/farmlandQualityAssessment",
-    "/dataStatistics",
-    
-    // 强制"重金属输入通量"和"重金属输出通量"下的所有路由为白色背景
-    ...tabs.value
-      .filter(tab => ["HmOutFlux", "hmInFlux"].includes(tab.name))
-      .flatMap(tab => tab.routes)
-  ];
-});
-
-// 判断当前路由是否在排除列表中
-const isExcludedRoute = computed(() => bgExcludedRoutes.value.includes(route.path));
-
-// 获取当前页面的背景图
-const getBgImage = (path: string) => {
-  // 若当前路径属于排除项,不显示背景图
-  if (isExcludedRoute.value) return "";
-  
-  return routeBackgroundMap[path] || "";
-};
-
-// 当前背景图
-watch(
-  () => route.path,
-  (newPath) => {
-    currentBgImage.value = getBgImage(newPath) ? `url(${getBgImage(newPath)})` : "";
-  },
-  { immediate: true }
-);
-
 // 点击切换 tab
 const handleClick = (tab: any, event: Event) => {
   activeAsideTab.value = tab.props.name;
@@ -429,12 +395,15 @@ const mainStyle = computed(() => {
 <style>
 /* 隐藏所有滚动条 */
 *::-webkit-scrollbar {
-  display: none; /* Chrome, Safari, Opera */
+  display: none;
+  /* Chrome, Safari, Opera */
 }
 
 * {
-  -ms-overflow-style: none; /* IE and Edge */
-  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none;
+  /* IE and Edge */
+  scrollbar-width: none;
+  /* Firefox */
 }
 
 /* 整体布局容器 */
@@ -444,6 +413,22 @@ const mainStyle = computed(() => {
   height: 100vh;
   overflow: hidden;
   position: relative;
+  background-color: #f5f7fa;
+  /* 默认背景色 */
+}
+
+/* 背景层 - 关键修改 */
+.background-layer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  filter: brightness(0.8);
 }
 
 /* 全屏页面特殊处理 */
@@ -452,39 +437,46 @@ const mainStyle = computed(() => {
   min-height: 100vh;
 }
 
-/* 背景层 - 用于透明部分 */
-.background-layer {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: url("@/assets/header-bg.jpg") center center / cover no-repeat;
-  background-attachment: fixed;
-  z-index: -1; /* 确保在内容下方 */
+/* 特殊背景页面的Header样式 */
+.transparent-header {
+  background: transparent !important;
+  backdrop-filter: blur(2px);
+  /* 添加轻微模糊效果增强可读性 */
+}
+
+/* 特殊背景页面的文字颜色 */
+.light-text {
+  color: #ffffff !important;
+  /* 白色文字 */
+  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
+  /* 文字阴影增强可读性 */
+}
+
+/* 特殊背景页面的头像边框 */
+.light-avatar-border {
+  border: 2px solid #ffffff !important;
+  /* 白色边框 */
+}
+
+/* 内容区域在特殊背景页面透明 */
+.transparent-scroll {
+  background-color: transparent !important;
 }
 
-/* 透明 Header */
+/* Header 样式 */
 .layout-header {
   height: 150px;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  background-color: transparent !important;
-  backdrop-filter: none !important;
-  -webkit-backdrop-filter: none !important;
-  border-bottom: none;
-  box-shadow: none !important;
-  color: #f0f3f7;
+  color: #333;
+  /* 深色文字 */
   flex-shrink: 0;
-  position: relative; /* 确保在背景层上方 */
-  z-index: 1;
-}
-
-/* 排除背景图页面的Header样式 */
-.excluded-bg-header {
-  background-color: #ffffff !important;
-  border-bottom: 1px solid #eee !important;
+  position: relative;
+  z-index: 2;
+  /* 确保在背景层上方 */
+  background-color: white;
+  /* 默认背景色 */
 }
 
 .logo-title-row {
@@ -506,38 +498,18 @@ const mainStyle = computed(() => {
   align-items: center;
   justify-content: flex-end;
   gap: 24px;
-  background-color: transparent !important;
-  box-shadow: none !important;
-  color: #ffffff;
+  color: #333;
+  /* 深色文字 */
   padding-top: 1px;
   position: static;
-  z-index: 100;
-}
-
-/* 排除背景图页面的文字颜色 */
-.excluded-text {
-  color: #333 !important;
-}
-
-/* 头像边框白色 */
-.el-dropdown-link .el-avatar {
-  border: 2px solid white;
-}
-
-/* 排除背景图页面的头像边框 */
-.excluded-avatar-border {
-  border: 2px solid #333 !important;
+  z-index: 1;
 }
 
 .welcome-text {
   font-size: 28px;
   font-weight: 500;
-  color: #ffffff !important;
-}
-
-/* 排除背景图页面的欢迎文字颜色 */
-.excluded-text .welcome-text {
   color: #333 !important;
+  /* 深色文字 */
 }
 
 /* Tab 区域 - 不透明 */
@@ -548,14 +520,11 @@ const mainStyle = computed(() => {
   align-items: center;
   padding: 0 24px;
   background: linear-gradient(to right, #1092d8, #02c3ad);
-  backdrop-filter: blur(10px);
-  -webkit-backdrop-filter: blur(10px);
   border-bottom: none !important;
-  box-shadow: none !important;
-  margin-bottom: 0 !important;
   flex-shrink: 0;
-  position: relative; /* 确保在背景层上方 */
-  z-index: 1;
+  position: relative;
+  z-index: 2;
+  /* 确保在背景层上方 */
 }
 
 /* el-tabs 外层容器 */
@@ -592,7 +561,6 @@ const mainStyle = computed(() => {
   padding: 0 20px !important;
   font-size: 20px;
   font-weight: 600;
-  color: #cfd8dc;
   border-radius: 10px;
   transition: all 0.2s ease-in-out;
   background-color: transparent;
@@ -638,12 +606,8 @@ const mainStyle = computed(() => {
   font-size: 48px;
   font-weight: bold;
   margin-top: 30px;
-  color: #f0f3f7;
-}
-
-/* 排除背景图页面的项目名称颜色 */
-.excluded-text.project-name {
-  color: #333 !important;
+  color: #333;
+  /* 深色文字 */
 }
 
 .layout-main-container {
@@ -653,19 +617,21 @@ const mainStyle = computed(() => {
   min-height: 0;
   position: relative;
   z-index: 1;
+  /* 确保在背景层上方 */
 }
 
-/* 侧边栏 - 不透明 */
+/* 侧边栏 - 白色背景 */
 .layout-aside {
   width: 360px;
-  background: linear-gradient(to bottom, #B7F1FC, #FFF8F0 );
+  background: linear-gradient(to bottom, #B7F1FC, #FFF8F0);
   border-right: 1px solid;
   overflow-y: auto;
   color: #000000;
   padding-top: 8px;
   height: 100%;
   position: relative;
-  z-index: 2; /* 确保在背景层上方 */
+  z-index: 2;
+  /* 确保在背景层上方 */
 }
 
 /* 隐藏侧边栏滚动条 */
@@ -707,19 +673,6 @@ const mainStyle = computed(() => {
   position: relative;
 }
 
-/* 排除背景图页面的内容区域 */
-.excluded-bg-content {
-  background-color: #ffffff !important;
-}
-
-/* 可滑动内容区域 */
-.scrollable-content {
-  flex: 1;
-  overflow: auto;
-  padding: 0 20px;
-  box-sizing: border-box;
-}
-
 /* 强制重置 el-tabs header 高度/边距/背景/阴影,避免背景层穿透错位 */
 .el-tabs__header.is-top {
   height: 48px !important;
@@ -741,9 +694,11 @@ const mainStyle = computed(() => {
   overflow: auto;
   padding: 0 20px;
   box-sizing: border-box;
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-position: center;
-  transition: background-image 0.3s ease-in-out;
+  background-color: white;
+  /* 默认背景色 */
+}
+
+.scrollable-content.transparent-scroll {
+  background-color: transparent;
 }
 </style>

+ 0 - 1
src/components/layout/isCollapse.ts

@@ -1,4 +1,3 @@
 import { ref } from 'vue';
 
 export const isCollapse = ref(false);
-

+ 2 - 2
src/components/layout/menuItems.ts

@@ -98,7 +98,7 @@ import {
       tab: 'HmOutFlux',
       children: [
         {
-          index: '/samplingDesc2',
+          index: '/farmInputSamplingDesc',
           label: '采样说明',
           icon: Sunny,
           tab: 'HmOutFlux'
@@ -118,7 +118,7 @@ import {
       tab: 'HmOutFlux',
       children: [
         {
-          index: '/samplingDesc3',
+          index: '/AtmosDepositionSamplingDesc',
           label: '采样说明',
           icon: Sunny,
           tab: 'HmOutFlux'

+ 7 - 7
src/router/index.ts

@@ -80,7 +80,7 @@ const routes = [
       {
         path: "samplingMethodDevice1",
         name: "samplingMethodDevice1",
-        component: () => import("@/views/User/heavyMetalFluxCalculation/inputFluxCalculation/waterdata/rivermessage.vue"), 
+        component: () => import("@/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue"), 
         meta: { title: "采样方法和装置" },
       },
       {
@@ -108,9 +108,9 @@ const routes = [
       //   meta: { title: "农产品投入" },
       // },
       {
-        path: "samplingDesc2",
-        name: "samplingDesc2",
-        component: () => import("@/views/User/HmOutFlux/agriInput/samplingDesc2.vue"),  
+        path: "farmInputSamplingDesc",
+        name: "farmInputSamplingDesc",
+        component: () => import("@/views/User/HmOutFlux/agriInput/farmInputSamplingDesc.vue"),  
         meta: { title: "采样说明" },
       },
       {
@@ -126,9 +126,9 @@ const routes = [
       //   meta: { title: "大气干湿沉降" },
       // },
       {
-        path: "samplingDesc3",
-        name: "samplingDesc3",
-        component: () => import("@/views/User/HmOutFlux/atmosDeposition/samplingDesc3.vue"), 
+        path: "AtmosDepositionSamplingDesc",
+        name: "AtmosDepositionSamplingDesc",
+        component: () => import("@/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue"), 
         meta: { title: "采样说明" },
       },
       {

+ 1 - 0
src/views/AboutView.vue

@@ -13,3 +13,4 @@
   }
 }
 </style>
+}

+ 237 - 0
src/views/User/HmOutFlux/agriInput/farmInputSamplingDesc.vue

@@ -0,0 +1,237 @@
+<template>
+  <div class="sampling-process">
+    <h2>1. 实地走访调查农业投入品使用情况:</h2>
+    <p>
+      实地走访调查项目实施区域作物种植面积、氮肥、磷肥、钾肥、复合肥、商品有机肥、农药和农家肥等年均施用量。
+      后期方案可利用无人机光谱识别技术调查项目区内的种植结构,再根据具体种植结构详细调查施肥习惯(如水稻田、蔬菜地等等)。
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image1" alt="农业投入品使用情况" class="sampling-image"></el-image>
+        <p class="image-caption">
+          图1 农业投入品使用情况
+        </p>
+      </div>
+    </div>
+
+    <h2>2.市面流通的农业投入品样品采集:</h2>
+    <p>
+      (1)合计采集市面上流通的及相关行业生产的各种农业投入品合计137份;<br>
+      (2)种类包含但不限于磷肥、钾肥、复合肥、叶面肥、常用农药、农家肥、地膜等;
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image2" alt="农业化肥采集" class="sampling-image"></el-image>
+        <p class="image-caption">
+          图2-1 农业化肥采集
+        </p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image3" alt="农药采集" class="sampling-image"></el-image>
+        <p class="image-caption">
+          图2-2 农药采集
+        </p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image4" alt="农膜采集" class="sampling-image"></el-image>
+        <p class="image-caption">
+          图2-3 农膜采集
+        </p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image5" alt="有机肥采集" class="sampling-image"></el-image>
+        <p class="image-caption">
+          图2-4 有机肥采集
+        </p>
+      </div>
+    </div>
+
+    <h2>3. 采集农业投入品样品重金属含量测试:</h2>
+    <p>
+      在完成农业投入品重金属输入通量计算公式的构建和优化后,最终的结果输出将包括各类农业投入品的重金属输入通量数据。
+      这些数据能够量化每种农业投入品在农田中所引入的重金属污染,并为后续的政策制定和管理提供依据。
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image6" alt="各农业投入品测试结果" class="sampling-image"></el-image>
+        <p class="image-caption">图3-1 各农业投入品测试结果</p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image7" alt="农业投入品镉含量" class="sampling-image"></el-image>
+        <p class="image-caption">图3-2 农业投入品镉含量</p>
+      </div>
+    </div>
+
+    <h2>4. 农业投入品重金属输入通量计算方法:</h2>
+    <p>
+      通过农业投入品的施用数据与重金属检测结果,建立重金属输入通量计算公式,精确计算不同农业投入品在农田中所引入的重金属污染通量。
+      该阶段包括公式的初步构建、验证与校准,以及公式的持续优化。
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image8" alt="农业投入品用量计算方法" class="sampling-image"></el-image>
+        <p class="image-caption">图4-1 农业投入品用量计算方法</p>
+        <div class="formula-text">
+          <p>
+            式中,Ni为农业投入品i的单位面积年施用量,单位:kg/ha/a;
+            Mi为农业投入品i年施用总量,单位:kg/a;
+            Ax为项目区域全年农作物总播种面积,单位:ha;
+          </p>
+        </div>
+      </div>
+      <div class="image-container">
+        <el-image :src="image9" alt="农业投入品重金属输入通量计算方法" class="sampling-image"></el-image>
+        <p class="image-caption">图4-2 农业投入品重金属输入通量计算方法</p>
+        <div class="formula-text">
+          <p>
+            式中,Ff为单位面积农业投入品途径的重金属输入通量,g/ha/a;
+            Ni为农业投入品i的单位面积年施用量,kg/ha/a;
+            Cij为重金属j在农业投入品i中的含量,mg/kg。
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      image1: '/农业投入品使用情况.png',
+      image2: '/农业化肥采集.png',
+      image3: '/农药采集.png',
+      image4: '/农膜采集.png',
+      image5: '/有机肥采集.png',
+      image6: '/各农业投入品测试结果.png',
+      image7: '/农业投入品镉含量.png',
+      image8: '/农业投入品用量计算方法.png',
+      image9: '/农业投入品重金属输入通量计算方法.png'
+    };
+  }
+};
+</script>
+
+<style scoped>
+.sampling-process {
+  padding: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  min-height: 100vh;
+  position: relative;
+  overflow: hidden;
+}
+
+.sampling-process::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
+  opacity: 0.3;
+  z-index: -1;
+}
+
+.image-row {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin: 30px 0;
+  gap: 20px;
+}
+
+p {
+  text-indent: 2em;
+  margin: 15px 0;
+  line-height: 1.6;
+}
+
+/* 特定段落取消缩进 */
+p:has(> br) {
+  text-indent: 0;
+}
+
+.image-container {
+  flex: 1;
+  min-width: 280px;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  background: rgba(255, 255, 255, 0.7);
+  border: 1px solid rgba(255, 255, 255, 0.5);
+  display: flex;
+  flex-direction: column;
+}
+
+.image-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  background: rgba(255, 255, 255, 0.85);
+}
+
+.sampling-image {
+  border-radius: 12px 12px 0 0 !important;
+  overflow: hidden;
+  border: none !important;
+  width: 100% !important;
+  height: 450px;
+  display: block;
+  transition: transform 0.5s ease;
+  background: rgba(255, 255, 255, 0.4);
+  object-fit: cover;
+}
+
+.image-container:hover .sampling-image {
+  transform: scale(1.03);
+}
+
+.image-caption {
+  text-align: center;
+  font-size: 15px;
+  color: #2d3748;
+  padding: 12px;
+  font-weight: 500;
+  background: rgba(248, 250, 252, 0.7);
+  margin: 0;
+}
+
+.formula-text {
+  padding: 15px;
+  background: rgba(245, 249, 255, 0.6);
+  border-top: 1px dashed #cbd5e0;
+}
+.formula-text p {
+  text-indent: 0;
+  font-size: 14px;
+  color: #1a365d;
+}
+
+/* 响应式设计 */
+@media (max-width: 900px) {
+  .image-container {
+    min-width: 48%;
+  }
+}
+
+@media (max-width: 768px) {
+  .image-container {
+    min-width: 100%;
+  }
+}
+
+@media (max-width: 480px) {
+  .sampling-process {
+    padding: 10px;
+  }
+
+  .sampling-image {
+    height: 200px;
+  }
+}
+</style>

+ 418 - 51
src/views/User/HmOutFlux/agriInput/prodInputFlux.vue

@@ -1,72 +1,73 @@
 <template>
   <div class="fertilizer-input-form">
-    <el-card shadow="always" class="form-card">
+    <!-- 输入表单部分 -->
+    <el-card v-if="showInputForm" shadow="always" class="form-card">
       <div class="card-content">
         <div class="input-section">
           <el-form label-width="250px" label-position="top">
             <div class="form-section">
-              <div class="input-group">
+                      <div class="input-group">
                 <el-form-item label="氮肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="nitrogenCdContent" placeholder="0.05"></el-input>
+                  <el-input v-model="formData.f3_nitrogen_cd_content" placeholder="0.05"></el-input>
                 </el-form-item>
                 <el-form-item label="氮肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="nitrogenUsage" placeholder="0.05"></el-input>
+                  <el-input v-model="formData.nf_nitrogen_usage" placeholder="0.05"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="磷肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="phosphorusCdContent" placeholder="0.158"></el-input>
+                  <el-input v-model="formData.f4_phosphorus_cd_content" placeholder="0.158"></el-input>
                 </el-form-item>
                 <el-form-item label="磷肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="phosphorusUsage" placeholder="0.158"></el-input>
+                  <el-input v-model="formData.pf_phosphorus_usage" placeholder="0.158"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="钾肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="potassiumCdContent" placeholder="0.06"></el-input>
+                  <el-input v-model="formData.f5_potassium_cd_content" placeholder="0.06"></el-input>
                 </el-form-item>
                 <el-form-item label="钾肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="potassiumUsage" placeholder="0.06"></el-input>
+                  <el-input v-model="formData.kf_potassium_usage" placeholder="0.06"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="复合肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="compoundFertilizerCdContent" placeholder="0.065"></el-input>
+                  <el-input v-model="formData.f6_compound_cd_content" placeholder="0.065"></el-input>
                 </el-form-item>
                 <el-form-item label="复合肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="compoundFertilizerUsage" placeholder="0.065"></el-input>
+                  <el-input v-model="formData.cf_compound_usage" placeholder="0.065"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="有机肥镉含量平均值 (mg/kg)" class="form-item">
-                  <el-input v-model="organicFertilizerCdContent" placeholder="0.6"></el-input>
+                  <el-input v-model="formData.f7_organic_cd_content" placeholder="0.6"></el-input>
                 </el-form-item>
                 <el-form-item label="有机肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="organicFertilizerUsage" placeholder="0.6"></el-input>
+                  <el-input v-model="formData.of_organic_usage" placeholder="0.6"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="农药镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="pesticideCdContent" placeholder="0.25"></el-input>
+                  <el-input v-model="formData.f8_pesticide_cd_content" placeholder="0.25"></el-input>
                 </el-form-item>
                 <el-form-item label="农药单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="pesticideUsage" placeholder="0.25"></el-input>
+                  <el-input v-model="formData.p_pesticide_usage" placeholder="0.25"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="农家肥镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="farmYardManureCdContent" placeholder="0.35"></el-input>
+                  <el-input v-model="formData.f9_farmyard_cd_content" placeholder="0.35"></el-input>
                 </el-form-item>
                 <el-form-item label="农家肥单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="farmYardManureUsage" placeholder="0.35"></el-input>
+                  <el-input v-model="formData.ff_farmyard_usage" placeholder="0.35"></el-input>
                 </el-form-item>
               </div>
               <div class="input-group">
                 <el-form-item label="农膜镉含量 (mg/kg)" class="form-item">
-                  <el-input v-model="agriFilmCdContent" placeholder="0.25"></el-input>
+                  <el-input v-model="formData.f10_film_cd_content" placeholder="0.25"></el-input>
                 </el-form-item>
                 <el-form-item label="农膜(存留)单位面积使用量 (t/ha/a)" class="form-item">
-                  <el-input v-model="agriFilmResidueUsage" placeholder="0.6"></el-input>
+                  <el-input v-model="formData.af_film_usage" placeholder="0.6"></el-input>
                 </el-form-item>
               </div>
             </div>
@@ -74,50 +75,348 @@
         </div>
         
         <div class="button-section">
-          <!-- 按钮区域背景图 -->
           <div class="button-bg"></div>
-          <!-- 底部半透明层 -->
           <div class="bottom-overlay"></div>
-          <el-button class="calculate-btn">
+          <el-button 
+            class="calculate-btn" 
+            @click="calculateAll"
+            :loading="loading"
+          >
             <span class="btn-text">农产品输入通量计算</span>
           </el-button>
         </div>
       </div>
     </el-card>
+    
+    <!-- 结果页面部分 -->
+    <div v-if="!showInputForm" class="results-page">
+      <!-- 返回按钮 -->
+      <el-button 
+        type="primary" 
+        class="back-button"
+        @click="showInputForm = true"
+      >
+        返回计算
+      </el-button>
+      <!-- 结果页面标题 -->
+      <h2 class="results-title">农业输入通量计算结果</h2>
+      
+      <!-- 自定义数据计算结果卡片 -->
+      <el-card class="result-card" v-if="customResult.success">
+        <h3>当前地区农业投入Cd通量计算结果</h3>
+        <p>总通量: {{ customResult.data.total_cd_flux }} g/ha/a</p>
+        <el-table :data="customResultDetails" border>
+          <el-table-column prop="type" label="投入类型"></el-table-column>
+          <el-table-column prop="flux" label="Cd通量(g/ha/a)"></el-table-column>
+        </el-table>
+        <div class="chart-container">
+            <div ref="customPieChart" style="width: 100%; height: 400px;"></div>
+          </div>
+      </el-card>
+      
+      <!-- 所有地区统计结果卡片 -->
+      <el-card class="result-card" v-if="allAreasResult.success">
+        <h3>所有地区农业投入Cd通量统计结果</h3>
+        <p>平均通量: {{ allAreasResult.data.summary.average_cd_flux }} g/ha/a</p>
+        <p>最高通量: {{ allAreasResult.data.summary.max_cd_flux.total_cd_flux }} g/ha/a ({{ allAreasResult.data.summary.max_cd_flux.area }})</p>
+        <p>最低通量: {{ allAreasResult.data.summary.min_cd_flux.total_cd_flux }} g/ha/a ({{ allAreasResult.data.summary.min_cd_flux.area }})</p>
+        
+        <el-table :data="allAreasList" border>
+          <el-table-column prop="area" label="地区"></el-table-column>
+          <el-table-column prop="total_cd_flux" label="Cd通量(g/ha/a)"></el-table-column>
+        </el-table>
+        <div class="chart-container">
+            <div ref="allAreasPieChart" style="width: 100%; height: 400px;"></div>
+          </div>
+      </el-card>
+    </div>
   </div>
 </template>
 
 <script>
+import axios from 'axios';
+import * as echarts from 'echarts'; 
+
 export default {
   data() {
     return {
-      nitrogenCdContent: '0.05',
-      nitrogenUsage: '0.05',
-      phosphorusCdContent: '0.158',
-      phosphorusUsage: '0.158',
-      potassiumCdContent: '0.06',
-      potassiumUsage: '0.06',
-      compoundFertilizerCdContent: '0.065',
-      compoundFertilizerUsage: '0.065',
-      organicFertilizerCdContent: '0.6',
-      organicFertilizerUsage: '0.6',
-      pesticideCdContent: '0.25',
-      pesticideUsage: '0.25',
-      farmYardManureCdContent: '0.35',
-      farmYardManureUsage: '0.35',
-      agriFilmCdContent: '0.25',
-      agriFilmResidueUsage: '0.6'
+      // 控制显示输入表单或结果页面
+      showInputForm: true,
+      
+      // 表单数据 - 使用API定义的字段名
+      formData: {
+        f3_nitrogen_cd_content: "0.12",
+        f4_phosphorus_cd_content: "0.85",
+        f5_potassium_cd_content: "0.05",
+        f6_compound_cd_content: "0.45",
+        f7_organic_cd_content: "0.22",
+        f8_pesticide_cd_content: "0.08",
+        f9_farmyard_cd_content: "0.15",
+        f10_film_cd_content: "0.03",
+        nf_nitrogen_usage: "0.25",
+        pf_phosphorus_usage: "0.15",
+        kf_potassium_usage: "0.12",
+        cf_compound_usage: "0.30",
+        of_organic_usage: "2.50",
+        p_pesticide_usage: "0.02",
+        ff_farmyard_usage: "1.80",
+        af_film_usage: "0.05",
+        description: "自定义数据计算结果"
+      },
+      
+      // 结果数据
+      loading: false,
+      customResult: {},
+      allAreasResult: {},
+
+      // ECharts实例
+      customPieChart: null,
+      allAreasPieChart: null
     };
+  },
+  computed: {
+    // 将自定义数据结果详情转换为表格数据
+    customResultDetails() {
+      if (!this.customResult.data || !this.customResult.data.details) return [];
+      return Object.entries(this.customResult.data.details).map(([type, flux]) => ({
+        type: this.getTypeName(type),
+        flux: flux
+      }));
+    },
+    
+    // 获取所有地区列表
+    allAreasList() {
+      if (!this.allAreasResult.data || !this.allAreasResult.data.results) return [];
+      return this.allAreasResult.data.results.map(area => ({
+        area: area.area,
+        total_cd_flux: area.total_cd_flux
+      }));
+    },
+    // 获取当前地区饼图数据
+    customPieData() {
+      if (!this.customResult.data || !this.customResult.data.details) return [];
+      return Object.entries(this.customResult.data.details).map(([type, value]) => ({
+        name: this.getTypeName(type),
+        value: value
+      }));
+    },
+    
+    // 获取所有地区饼图数据(取前8个地区)
+    allAreasPieData() {
+      if (!this.allAreasList || this.allAreasList.length === 0) return [];
+      
+      // 复制并排序
+      const sortedAreas = [...this.allAreasList].sort((a, b) => b.total_cd_flux - a.total_cd_flux);
+      
+      // 取前8个地区
+      return sortedAreas.slice(0, 8).map(area => ({
+        name: area.area,
+        value: area.total_cd_flux
+      }));
+    }
+  },
+  methods: {
+    // 计算所有数据
+    async calculateAll() {
+      try {
+        this.loading = true;
+        
+        // 准备自定义数据请求体
+        const requestBody = {
+          ...this.formData,
+          // 将字符串值转换为数字
+          f3_nitrogen_cd_content: parseFloat(this.formData.f3_nitrogen_cd_content),
+          f4_phosphorus_cd_content: parseFloat(this.formData.f4_phosphorus_cd_content),
+          f5_potassium_cd_content: parseFloat(this.formData.f5_potassium_cd_content),
+          f6_compound_cd_content: parseFloat(this.formData.f6_compound_cd_content),
+          f7_organic_cd_content: parseFloat(this.formData.f7_organic_cd_content),
+          f8_pesticide_cd_content: parseFloat(this.formData.f8_pesticide_cd_content),
+          f9_farmyard_cd_content: parseFloat(this.formData.f9_farmyard_cd_content),
+          f10_film_cd_content: parseFloat(this.formData.f10_film_cd_content),
+          nf_nitrogen_usage: parseFloat(this.formData.nf_nitrogen_usage),
+          pf_phosphorus_usage: parseFloat(this.formData.pf_phosphorus_usage),
+          kf_potassium_usage: parseFloat(this.formData.kf_potassium_usage),
+          cf_compound_usage: parseFloat(this.formData.cf_compound_usage),
+          of_organic_usage: parseFloat(this.formData.of_organic_usage),
+          p_pesticide_usage: parseFloat(this.formData.p_pesticide_usage),
+          ff_farmyard_usage: parseFloat(this.formData.ff_farmyard_usage),
+          af_film_usage: parseFloat(this.formData.af_film_usage)
+        };
+        
+        // 同时调用两个API
+        const [customResponse, allAreasResponse] = await Promise.all([
+          axios.post(
+            'http://localhost:8000/api/agricultural-input/calculate-with-custom-data', 
+            requestBody
+          ),
+          axios.get(
+            'http://localhost:8000/api/agricultural-input/calculate-all-areas'
+          )
+        ]);
+        
+        // 处理自定义数据结果
+        this.customResult = customResponse.data;
+        if (!this.customResult.success) {
+          this.$message.error(this.customResult.message || '自定义数据计算失败');
+        }
+        
+        // 处理所有地区结果
+        this.allAreasResult = allAreasResponse.data;
+        if (!this.allAreasResult.success) {
+          this.$message.error(this.allAreasResult.message || '计算所有地区失败');
+        }
+        
+        // 切换到结果页面
+        this.showInputForm = false;
+
+         // 等待DOM更新
+        this.$nextTick(() => {
+          this.initCharts();
+        });
+      } catch (error) {
+        console.error('API调用错误:', error);
+        this.$message.error('计算失败,请检查网络连接');
+      } finally {
+        this.loading = false;
+      }
+    },
+    
+    // 获取类型的中文名称
+    getTypeName(type) {
+      const typeNames = {
+        'nitrogen_fertilizer': '氮肥',
+        'phosphorus_fertilizer': '磷肥',
+        'potassium_fertilizer': '钾肥',
+        'compound_fertilizer': '复合肥',
+        'organic_fertilizer': '有机肥',
+        'pesticide': '农药',
+        'farmyard_manure': '农家肥',
+        'agricultural_film': '农膜'
+      };
+      return typeNames[type] || type;
+    },
+     // 初始化图表
+    initCharts() {
+      // 销毁已有实例
+      if (this.customPieChart) {
+        this.customPieChart.dispose();
+      }
+      if (this.allAreasPieChart) {
+        this.allAreasPieChart.dispose();
+      }
+      
+      // 创建新的图表实例
+      this.customPieChart = echarts.init(this.$refs.customPieChart);
+      this.allAreasPieChart = echarts.init(this.$refs.allAreasPieChart);
+      
+      // 设置图表选项
+      this.customPieChart.setOption(this.getPieChartOption('当前地区各项投入通量占比', this.customPieData));
+      this.allAreasPieChart.setOption(this.getPieChartOption('各地区通量占比', this.allAreasPieData));
+      
+      // 响应窗口大小变化
+      window.addEventListener('resize', this.onResize);
+    },
+    
+    // 获取饼图配置
+    getPieChartOption(title, data) {
+      return {
+        title: {
+          text: title,
+          left: 'center',
+          textStyle: {
+            fontSize: 16,
+            fontWeight: 'bold'
+          }
+        },
+        tooltip: {
+          trigger: 'item',
+          formatter: '{a} <br/>{b}: {c} g/ha/a ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          right: 10,
+          top: 'center',
+          data: data.map(item => item.name)
+        },
+        series: [
+          {
+            name: title,
+            type: 'pie',
+            radius: ['40%', '70%'],
+            center: ['40%', '50%'],
+            avoidLabelOverlap: false,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: '#fff',
+              borderWidth: 2
+            },
+            label: {
+              show: false,
+              position: 'center'
+            },
+            emphasis: {
+              label: {
+                show: true,
+                fontSize: '16',
+                fontWeight: 'bold',
+                formatter: '{b}\n{c} g/ha/a\n{d}%'
+              }
+            },
+            labelLine: {
+              show: false
+            },
+            data: data
+          }
+        ]
+      };
+    },
+    
+    // 响应窗口大小变化
+    onResize() {
+      if (this.customPieChart) {
+        this.customPieChart.resize();
+      }
+      if (this.allAreasPieChart) {
+        this.allAreasPieChart.resize();
+      }
+    }
+  },
+  watch: {
+    // 当结果数据变化时更新图表
+    customPieData() {
+      if (this.customPieChart) {
+        this.customPieChart.setOption(this.getPieChartOption('当前地区各项投入通量占比', this.customPieData));
+      }
+    },
+    allAreasPieData() {
+      if (this.allAreasPieChart) {
+        this.allAreasPieChart.setOption(this.getPieChartOption('各地区通量占比', this.allAreasPieData));
+      }
+    }
+  },
+  beforeUnmount() {
+    // 组件销毁前移除事件监听
+    window.removeEventListener('resize', this.onResize);
+    
+    // 销毁图表实例
+    if (this.customPieChart) {
+      this.customPieChart.dispose();
+    }
+    if (this.allAreasPieChart) {
+      this.allAreasPieChart.dispose();
+    }
   }
 };
 </script>
 
 <style scoped>
+/* 原有样式保持不变 */
 .fertilizer-input-form {
   padding: 20px;
   display: flex;
-  justify-content: center;
-  background-color: rgba(255, 255, 255, 0.8); /* 半透明背景 */
+  flex-direction: column;
+  align-items: center;
+  background-color: rgba(255, 255, 255, 0.8);
 }
 
 .form-card {
@@ -152,20 +451,17 @@ export default {
   overflow: hidden;
 }
 
-/* 按钮区域背景图 - 使用本地图片 */
 .button-bg {
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
-  /* 请替换为您的本地图片路径 */
   background: url('@/assets/images/fertilizer-bg.jpg') no-repeat center center;
   background-size: cover;
   z-index: 0;
 }
 
-/* 底部半透明层 */
 .bottom-overlay {
   position: absolute;
   left: 0;
@@ -210,28 +506,23 @@ export default {
   width: 100%;
 }
 
-/* 使用 :deep() 替代已弃用的 >>> */
 :deep(.el-input) .el-input__inner {
   width: 100% !important;
   padding: 12px 0;
   border: none;
   border-radius: 0;
   background: transparent;
-  /* 底部边框效果 */
   border-bottom: 1px solid #dcdfe6;
-  /* 底部阴影效果 */
   box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
   transition: all 0.3s ease;
 }
 
 :deep(.el-input) .el-input__inner:focus {
   border-bottom: 2px solid #409EFF;
-  /* 聚焦时底部阴影加强 */
   box-shadow: 0 2px 0 rgba(64, 158, 255, 0.2);
   background: rgba(64, 158, 255, 0.03);
 }
 
-/* 占位符样式 */
 :deep(.el-input) .el-input__inner::placeholder {
   color: #a0a0a0;
   font-style: italic;
@@ -247,11 +538,8 @@ export default {
   font-weight: bold;
   transition: all 0.4s ease;
   position: relative;
-  z-index: 2; /* 确保按钮在覆盖层之上 */
-  
-  /* 渐变背景色 */
+  z-index: 2;
   background: linear-gradient(to right, #8DF9F0, #26B046);
-  /* 按钮整体阴影 */
   box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
               0 4px 10px rgba(38, 176, 70, 0.3) inset;
 }
@@ -280,4 +568,83 @@ export default {
   text-align: center;
   line-height: 1.4;
 }
+
+/* 结果页面样式 */
+.results-page {
+  width: 90%;
+  max-width: 1200px;
+  padding: 30px;
+  background: linear-gradient(135deg, #FAFDFF, #FFFAA2);
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.results-title {
+  text-align: center;
+  color: #333;
+  margin-bottom: 30px;
+  font-size: 28px;
+}
+
+/* 结果卡片样式 */
+.result-card {
+  width: 90%;
+  max-width: 1200px;
+  margin: 0 auto;
+  background: linear-gradient(135deg, #FAFDFF, #FFFAA2);
+  border: 1px solid #e6e6e6;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+}
+
+.result-card h3 {
+  text-align: center;
+  margin-bottom: 20px;
+  color: #333;
+}
+
+.result-card p {
+  margin: 10px 0;
+  font-size: 16px;
+}
+
+/* 表格样式 */
+.result-table {
+  padding: 20px;
+}
+
+.result-table .el-table {
+  width: 100%;
+  margin-top: 15px;
+  margin-bottom: 20px;
+}
+
+/* 图表容器 */
+.chart-container {
+  margin-top: 20px;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  padding: 10px;
+  background: #f9f9f9;
+}
+
+/* 返回按钮样式 */
+.back-button {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  width: 120px;
+  font-size: 16px;
+  padding: 10px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
+  border: none;
+  border-radius: 20px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.back-button:hover {
+  background: linear-gradient(to right, #7de8df, #20a03d);
+}
 </style>

+ 0 - 25
src/views/User/HmOutFlux/agriInput/samplingDesc2.vue

@@ -1,25 +0,0 @@
-<template>
-  <div class="">
-    
-  </div>
-</template>
-
-<script>
-export default {
-  name: '',
-  data() {
-    return {
-      
-    };
-  },
-  methods: {
-    
-  }
-};
-</script>
-
-<style scoped>
-  . {
-    
-  }
-</style>

+ 186 - 0
src/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="sampling-process">
+    <h2>1.建立大气污染源清单:</h2>
+    <p>
+      <strong>污染源统计:</strong>
+      根据韶关市环保部门或政府官方网站公开资料及全国排污许可证管理信息平台,
+      对韶关具有排污许可证的企业进行数据统计与调查分析;
+    </p>
+    <p>
+      <strong>污染源统计:</strong>
+      根据韶关市环保部门或政府官方网站公开资料及全国排污许可证管理信息平台,
+      对韶关具有排污许可证的企业进行数据统计与调查分析;
+    </p>
+    <p>
+      <strong>行业调查与筛选:</strong>
+      针对可能存在重金属排放的行业进行全面调查和筛选。这些行业包括燃煤电厂、蓄电池制造、危险废物治理、矿山采选、
+      有色金属冶炼、化工、电子电路制造等。
+    </p>
+    <p>
+      <strong>企业调查与统计分析:</strong>
+      对所有重金属排放企业进行详细调查和统计分析,包括但不限于企业名称、地址坐标、生产工艺、主要产品、排放设施、
+      排放标准等信息,建立源清单的详细数据库。对企业的生产规模、产值、用地情况等进行综合评估,确定其在污染排放中的影响程度。
+    </p>
+    <p>
+      <strong>排放数据收集:</strong>
+      收集以上所有企业的大气排放数据,包括但不限于污染物排放许可值、重金属排放总量、大气颗粒物排放总量、氮氧化物排放总量、
+      硫化物排放总量等大气污染物。获取数据的来源包括企业排污许可证、环保部门监管数据、企业自行监测报告、第三方监测机构数据等。
+    </p>
+    <p>
+      <strong>数据验证与质量控制:</strong>
+      对收集到的排放数据进行查找验证和质量控制,确保数据的准确性和可靠性。检查数据的完整性和一致性,
+      排除可能存在的错误和异常数据,提高数据的可信度。
+    </p>
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image1" alt="大气干湿沉降示意图" class="sampling-image"></el-image>
+        <p class="image-caption">图1 大气干湿沉降示意图</p>
+      </div>
+    </div>
+
+    <h2>2.现场调研:</h2>
+    <p>
+      制定重金属排放源的详细调研计划,包括调研时间、地点、人员分工、调研方法等。根据研究目的和问题,
+      选择合适的调研方法,如问卷调查、访谈、观察法等。按照调研计划,前往现场进行调研,对企业进行前期的实地考察,
+      了解能源使用、生产工艺及废气排放情况。对企业所在区域进行调查,大气排放是否会对周围区域产生影响。在调研过程中,
+      注意信息的获取与保存,比如拍照、排污口测距、风向记录等等。
+    </p>
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image2" alt="干湿沉降收集装置" class="sampling-image"></el-image>
+        <p class="image-caption">图2 干湿沉降收集装置</p>
+      </div>
+    </div>
+
+    <h2>3.样品分析:</h2>
+    <p>
+      <strong>重金属检测技术:</strong>
+      例如原子吸收光谱法(AAS)、电感耦合等离子体质谱法(ICP-MS)、X射线荧光光谱法(XRF)等。
+    </p>
+    <p>
+      <strong>数据处理与分析:</strong>
+      质量控制和数据分析的统计方法。
+    </p>
+
+
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      image1: '/大气干湿沉降示意图.png',
+      image2: '/干湿沉降收集装置.png',
+    };
+  }
+};
+</script>
+
+<style scoped>
+.sampling-process {
+  padding: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  min-height: 100vh;
+  position: relative;
+  overflow: hidden;
+}
+
+.sampling-process::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
+  opacity: 0.3;
+  z-index: -1;
+}
+
+.image-row {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin: 30px 0;
+  gap: 20px;
+}
+
+p {
+  text-indent: 2em;
+}
+
+.image-container {
+  flex: 1;
+  min-width: 280px;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  background: rgba(255, 255, 255, 0.7);
+  border: 1px solid rgba(255, 255, 255, 0.5);
+
+  /* 新增:使用 flex 布局,垂直方向堆叠,内容居中 */
+  display: flex;
+  flex-direction: column;
+  align-items: center; /* 水平居中 */
+}
+
+.image-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  background: rgba(255, 255, 255, 0.85);
+}
+
+.sampling-image {
+  border-radius: 12px 12px 0 0 !important;
+  overflow: hidden;
+  border: none !important;
+  width: 100%;     /* 改为 100% 宽度,填满容器 */
+  max-width: 600px; 
+  display: block;
+  transition: transform 0.5s ease;
+  background: rgba(255, 255, 255, 0.4);
+
+  /* 新增:居中对齐 */
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.image-container:hover .sampling-image {
+  transform: scale(1.03);
+}
+
+.image-caption {
+  text-align: center;
+  font-size: 15px;
+  color: #2d3748;
+  padding: 12px;
+  font-weight: 500;
+  background: rgba(248, 250, 252, 0.7);
+  margin: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 900px) {
+  .image-container {
+    min-width: 48%;
+  }
+}
+
+@media (max-width: 768px) {
+
+  .image-container {
+    min-width: 100%;
+  }
+}
+
+@media (max-width: 480px) {
+  .sampling-process {
+    padding: 10px;
+  }
+
+  .sampling-image {
+    height: 200px;
+  }
+}
+</style>

+ 6 - 6
src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue

@@ -26,7 +26,7 @@
    </div>
 
    <div>
-    <div class="component-title">各区县企业平均大气颗粒物排放(t/a)</div>
+    <div class="component-title">各区县平均大气重金属污染柱状图</div>
     <AirsampleChart :calculation-method="calculationMethod"/>
    </div>
   </div>
@@ -35,9 +35,9 @@
 
 <script setup>
 import {ref} from 'vue'
-import AirsampleLine from './airsampleLine.vue';
-import atmsamplemap from './atmsamplemap.vue';
-import AirsampleChart from './airsampleChart.vue';
+import AirsampleLine from '@/components/atmpollution/airsampleLine.vue';
+import atmsamplemap from '@/components/atmpollution/atmsamplemap.vue';
+import AirsampleChart from '@/components/atmpollution/airsampleChart.vue';
 
 const calculationMethod = ref('weight')
 
@@ -73,7 +73,7 @@ const calculationMethod = ref('weight')
 
 .map-holder {
   position: relative;
-  height: 600px;
+  height: 450px;
   z-index: 100; /* 保持地图在中间层级 */
   overflow: visible; /* 允许内容溢出 */
 }
@@ -111,7 +111,7 @@ const calculationMethod = ref('weight')
   position: relative;      /* 为伪元素定位做准备 */
 
   /* 文字样式:简约但醒目 */
-  font-size: 1.7rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
+  font-size: 1.5rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
   font-weight: 600;        /* 适度加粗,比正文突出但不夸张 */
   color: #1e88e5;          /* 统一蓝色,和方块颜色呼应 */
   line-height: 1.2;        /* 紧凑行高,避免臃肿 */

+ 0 - 351
src/views/User/HmOutFlux/atmosDeposition/airsampleChart.vue

@@ -1,351 +0,0 @@
-<template>
-  <div class="atmosphere-summary">
-    <div ref="chartRef" class="chart-box"></div>
-    <div v-if="loading" class="status">数据加载中...</div>
-    <div v-else-if="error" class="status error">{{ error }}</div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted, watch } from 'vue'
-import * as echarts from 'echarts'
-import axios from 'axios'
-
-// 接收父组件传递的计算方式
-const props = defineProps({
-  calculationMethod: {
-    type: String,
-    required: true,
-    default: 'weight' // 默认按重量计算
-  }
-})
-
-// 仅使用一个接口(包含位置和数据信息)
-const SUMMARY_API = 'http://localhost:3000/table/Atmosphere_summary_data';
-
-// 配置项
-const COLORS = ['#66CCFF', '#FF9966', '#99CC99', '#CC99CC', '#FFCC66', '#6699CC', '#FF6666', '#99CC66'];
-
-// 地区白名单 - 根据实际情况修改
-const REGIONS = [
-  '浈江区', '武江区', '曲江区', '乐昌市', 
-  '南雄市', '始兴县', '仁化县', '翁源县', 
-  '新丰县', '乳源瑶族自治县'
-];
-
-// 响应式数据
-const chartRef = ref(null);
-const loading = ref(true);
-const error = ref('');
-let myChart = null;
-
-// 根据计算方式获取对应的指标
-const getMetricsByMethod = (method) => {
-  if (method === 'weight') {
-    return [
-      'Cr mg/kg',
-      'As mg/kg',
-      'Cd mg/kg',
-      'Hg mg/kg',
-      'Pb mg/kg',
-    ]
-  } else {
-    return [
-      'Cr ug/m3',
-      'As ug/m3',
-      'Cd ug/m3',
-      'Hg ug/m3',
-      'Pb ug/m3'
-    ]
-  }
-}
-
-// 地区提取函数(从位置信息中解析地区)
-const extractRegion = (location) => {
-  if (!location || typeof location !== 'string') return '未知区县';
-
-  // 精确匹配官方区县名称
-  const officialMatch = REGIONS.find(region => 
-    location.includes(region)
-  );
-  if (officialMatch) return officialMatch;
-
-  // 处理嵌套格式(如"韶关市-浈江区")
-  const nestedMatch = location.match(/(韶关市)([^市]+?[区市县])/);
-  if (nestedMatch && nestedMatch[2]) {
-    const region = nestedMatch[2].replace("韶关市", "").trim();
-    const validRegion = REGIONS.find(r => r.includes(region));
-    if (validRegion) return validRegion;
-  }
-
-  // 特殊格式处理(如"韶关市浈江区")
-  const shortMatch = location.match(/韶关市([区市县][^市]{2,5})/);
-  if (shortMatch && shortMatch[1]) return shortMatch[1];
-
-  // 修正常见拼写错误
-  if (location.includes('乐昌')) return '乐昌市';
-  if (location.includes('乳源')) return '乳源瑶族自治县';
-
-  console.warn(`未识别地区: ${location}`);
-  return '未知区县';
-};
-
-// 数据处理流程(仅依赖单个接口)
-const processData = async () => {
-  try {
-    // 获取当前计算方式对应的指标
-    const metrics = getMetricsByMethod(props.calculationMethod);
-    
-    // 仅请求汇总数据接口(包含位置和指标信息)
-    const response = await axios.get(SUMMARY_API, { timeout: 10000 });
-    const summaryData = response.data;
-
-    if (!Array.isArray(summaryData) || summaryData.length === 0) {
-      throw new Error('没有获取到有效数据');
-    }
-
-    // 1. 为每个数据项添加地区信息(从自身位置字段提取)
-    const dataWithRegion = summaryData.map(item => ({
-      ...item,
-      // 位置信息字段为 采样,根据实际字段名修改
-      region: extractRegion(item.采样 || '')
-    }));
-
-    // 2. 按区县分组统计
-    const regionGroups = {};
-    const cityWideAverages = {}; // 全市平均值
-    const uniqueSampleIds = new Set();
-    
-    // 初始化统计计数器
-    metrics.forEach(metric => {
-      cityWideAverages[metric] = { sum: 0, count: 0 };
-    });
-    
-    dataWithRegion.forEach(item => {
-      const region = item.region;
-      
-      // 样本唯一标识字段为 样品名称,根据实际字段名修改
-      if (item.样品名称) {
-        uniqueSampleIds.add(item.样品名称);
-      }
-
-      // 初始化地区分组
-      if (!regionGroups[region]) {
-        regionGroups[region] = {};
-        metrics.forEach(metric => {
-          regionGroups[region][metric] = { sum: 0, count: 0 };
-        });
-      }
-
-      // 统计各指标值
-      metrics.forEach(metric => {
-        const val = parseFloat(item[metric]);
-        if (!isNaN(val)) {
-          // 更新地区统计
-          regionGroups[region][metric].sum += val;
-          regionGroups[region][metric].count++;
-          
-          // 更新全市统计
-          cityWideAverages[metric].sum += val;
-          cityWideAverages[metric].count++;
-        }
-      });
-    });
-    
-    const totalSamples = uniqueSampleIds.size;
-
-    // 3. 按官方顺序排序区县
-    const regions = REGIONS.filter(region => regionGroups[region]);
-    
-    // 4. 添加"全市平均"作为最后一个类别
-    regions.push("全市平均");
-
-    // 5. 构建ECharts数据
-    const series = metrics.map((metric, idx) => {
-      // 计算全市平均值
-      const cityWideAvg = cityWideAverages[metric].count 
-        ? (cityWideAverages[metric].sum / cityWideAverages[metric].count).toFixed(5) 
-        : 0;
-      
-      return {
-        name: metric,
-        type: 'bar',
-        data: regions.map(region => {
-          if (region === "全市平均") {
-            return cityWideAvg;
-          }
-          const group = regionGroups[region][metric];
-          return group.count ? (group.sum / group.count).toFixed(5) : 0;
-        }),
-        itemStyle: { 
-          color: COLORS[idx % COLORS.length],
-        },
-        label: {
-          show: true,
-          position: 'top',
-          fontSize: 20,
-          color: '#333',
-        }
-      };
-    });
-
-    return { regions, series, totalSamples, metrics };
-  } catch (err) {
-    error.value = '数据处理失败: ' + (err.message || '未知错误');
-    console.error('数据处理错误:', err);
-    return null;
-  }
-};
-
-// 初始化图表
-const initChart = async () => {
-  loading.value = true;
-  error.value = '';
-  
-  try {
-    const processedData = await processData();
-    if (!processedData) return;
-    
-    const { regions, series, totalSamples, metrics } = processedData;
-    
-    if (!chartRef.value) return;
-    if (myChart) myChart.dispose();
-
-    myChart = echarts.init(chartRef.value);
-    
-    // 根据计算方式设置标题
-    const titleText = props.calculationMethod === 'weight' 
-      ? '各区域空气颗粒物重量指标平均值' 
-      : '各区域空气颗粒物体积指标平均值';
-    
-    const option = {
-      title: { 
-        text: titleText,
-        left: 'center',
-        subtext: `数据来源: ${totalSamples}个有效检测样本`,
-        subtextStyle: {
-          fontSize: 18
-        }
-      },
-      tooltip: { 
-        trigger: 'axis',
-        formatter: params => {
-          const regionName = params[0].name;
-          const isCityWide = regionName === "全市平均";
-          
-          let content = `${isCityWide ? "全市平均值" : regionName}:`;
-          if (isCityWide) {
-            content += `<br><span style="color: #666;">(基于${totalSamples}个样本计算)</span>`;
-          }
-          
-          return content + params.map(p => `<br>${p.seriesName}: ${p.value}`).join('');
-        }
-      },
-      xAxis: {
-        type: 'category',
-        data: regions,
-        axisLabel: { 
-          rotate: 45,
-          formatter: val => val.replace('韶关市', ''),
-          fontSize: 20
-        }
-      },
-      yAxis: { 
-        type: 'value',
-        axisLabel: { fontSize: 20 },
-        axisLine: { lineStyle: { width: 1.5 } },
-        splitLine: { lineStyle: { opacity: 0.3 } }
-      },
-      dataZoom: [{
-        type: 'inside',
-        start: 0,
-        end: 100
-      }],
-      series,
-      legend: { 
-        data: metrics, 
-        bottom: 10,
-        textStyle: {
-          fontSize: 20
-        }
-      },
-      grid: { 
-        left: '5%', 
-        right: '5%', 
-        bottom: '15%', 
-        top: '10%',
-        containLabel: true 
-      }
-    };
-
-    myChart.setOption(option);
-  } catch (err) {
-    error.value = '图表初始化失败: ' + (err.message || '未知错误');
-    console.error('图表错误:', err);
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 监听计算方式变化,重新渲染图表
-watch(
-  () => props.calculationMethod,
-  () => {
-    initChart();
-  }
-)
-
-// 生命周期钩子
-onMounted(() => {
-  initChart();
-  // 窗口大小变化时重绘图表
-  window.addEventListener('resize', () => {
-    if (myChart) myChart.resize();
-  });
-});
-
-onUnmounted(() => {
-  if (myChart) {
-    myChart.dispose();
-    myChart = null;
-  }
-  window.removeEventListener('resize', () => {
-    if (myChart) myChart.resize();
-  });
-});
-</script>
-
-<style scoped>
-.atmosphere-summary {
-  width: 100%;
-  max-width: 1400px;
-  margin: 0 auto;
-  padding: 20px;
-  box-sizing: border-box;
-  position: relative;
-}
-
-.chart-box {
-  width: 100%;
-  height: 600px;
-  min-height: 400px;
-  background-color: white;
-  border-radius: 8px;
-}
-
-.status {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  padding: 15px;
-  background: rgba(255,255,255,0.8);
-  border-radius: 4px;
-  font-size: 16px;
-}
-
-.error { 
-  color: #ff4d4f;
-  font-weight: bold;
-}
-</style>

+ 0 - 300
src/views/User/HmOutFlux/atmosDeposition/airsampleLine.vue

@@ -1,300 +0,0 @@
-<template>
-  <div class="container mx-auto px-4 py-8">
-    <div class="bg-white rounded-xl shadow-lg overflow-hidden">
-      <div class="p-6 border-b border-gray-200">
-        <h1 class="text-[clamp(1rem,3vw,2.5rem)] font-bold text-gray-800 text-center">大气污染采样点列表</h1>
-      </div>
-      
-      <!-- 加载状态 -->
-      <div v-if="loading" class="py-20 flex justify-center items-center">
-        <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
-      </div>
-      
-      <!-- 错误状态 -->
-      <div v-else-if="error" class="p-8 bg-red-50 border-l-4 border-red-400 text-red-700">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <i class="fa fa-exclamation-triangle text-red-500 text-xl"></i>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-medium text-red-800">加载失败</h3>
-            <div class="mt-2 text-sm text-red-700">
-              <p>{{ error }}</p>
-            </div>
-            <div class="mt-4">
-              <button @click="fetchData" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150 ease-in-out">
-                <i class="fa fa-refresh mr-2"></i>重试
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-      
-      <!-- 数据表格(根据计算方式动态显示列) -->
-      <div v-else-if="filteredData.length > 0" class="overflow-x-auto">
-        <table class="min-w-full divide-y divide-gray-200">
-          <thead class="bg-gray-50">
-            <tr>
-              <!-- 动态渲染当前计算方式对应的列 -->
-              <th 
-                v-for="(col, index) in displayColumns" 
-                :key="index"
-                class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors"
-                @click="sortData(col.key)"
-              >
-                <div class="flex items-center justify-between">
-                  {{ col.label }}
-                  <span v-if="sortKey === col.key" class="ml-1 text-gray-400">
-                    {{ sortOrder === 'asc' ? '↑' : '↓' }}
-                  </span>
-                </div>
-              </th>
-            </tr>
-          </thead>
-          <tbody class="bg-white divide-y divide-gray-200">
-            <tr v-for="(item, rowIndex) in sortedData" :key="rowIndex" 
-                class="hover:bg-gray-50 transition-colors duration-150">
-              <!-- 动态渲染当前计算方式对应的列数据 -->
-              <td 
-                v-for="(col, colIndex) in displayColumns" 
-                :key="colIndex"
-                class="px-6 py-4 whitespace-nowrap text-sm"
-              >
-                <div class="flex items-center">
-                  <div class="text-gray-900 font-medium">
-                    <!-- 体积数据保持原始格式,-->
-                    {{ formatValue(item[col.key], col.isVolume,col.type) }}
-                  </div>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
-      
-      <!-- 空数据状态(保持不变) -->
-      <div v-else class="p-8 text-center">
-        <div class="flex flex-col items-center justify-center">
-          <div class="text-gray-400 mb-4">
-            <i class="fa fa-database text-5xl"></i>
-          </div>
-          <h3 class="text-lg font-medium text-gray-900 mb-1">暂无有效数据</h3>
-          <p class="text-gray-500">已过滤全空行</p>
-        </div>
-      </div>
-  
-      <!-- 数据统计 -->
-      <div class="p-4 bg-gray-50 border-t border-gray-200">
-         <div class="flex flex-col md:flex-row justify-between items-center">
-      <div class="text-sm text-gray-500 mb-2 md:mb-0">
-        共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
-      </div>
-    </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted, defineProps } from 'vue';
-import axios from 'axios';
-
-// 1. 接收父组件传递的计算方式(重量/体积)
-const props = defineProps({
-  calculationMethod: {
-    type: String,
-    required: true,
-    default: 'weight' // 默认按重量计算
-  }
-});
-
-// 2. 定义重量/体积对应的列配置(核心:区分两类指标)
-// 公共列(两种方式都显示的基础信息)
-const commonColumns = [
-  { key: '采样', label: '采样位置', isVolume: false,type:'string' }, // 基础信息,不属于任何分类
-  { key: '样品名称', label: '样品名称', isVolume: false ,type:'string'}
-];
-
-// 重量相关列(isVolume标记为false)
-const weightColumns = [
-  { key: 'Cr mg/kg', label: 'Cr mg/kg', isVolume: false,type:'number' },
-  { key: 'As mg/kg', label: 'As mg/kg', isVolume: false ,type:'number'},
-  { key: 'Cd mg/kg', label: 'Cd mg/kg', isVolume: false ,type:'number'},
-  { key: 'Hg mg/kg', label: 'Hg mg/kg', isVolume: false ,type:'number'},
-  { key: 'Pb mg/kg', label: 'Pb mg/kg', isVolume: false ,type:'number'},
-  { key: '颗粒物的重量 mg', label: '颗粒物的重量 mg', isVolume: false ,type:'number'}
-];
-
-// 体积相关列(isVolume标记为true)
-const volumeColumns = [
-  { key: '标准体积 m3', label: '标准体积 m³', isVolume: true ,type:'number'},
-  { key: '颗粒物浓度ug/m3', label: '颗粒物浓度ug/m³', isVolume: true ,type:'number'},
-  { key: 'Cr ug/m3', label: 'Cr ug/m³', isVolume: true ,type:'number'},
-  { key: 'As ug/m3', label: 'As ug/m³', isVolume: true ,type:'number'},
-  { key: 'Cd ug/m3', label: 'Cd ug/m³', isVolume: true ,type:'number'},
-  { key: 'Hg ug/m3', label: 'Hg ug/m³', isVolume: true ,type:'number'},
-  { key: 'Pb ug/m3', label: 'Pb ug/m³', isVolume: true ,type:'number'}
-];
-
-// 3. 根据计算方式动态生成显示列(公共列 + 对应分类列)
-const displayColumns = computed(() => {
-  if (props.calculationMethod === 'volume') {
-    return [...commonColumns, ...volumeColumns]; // 体积模式:公共列 + 体积列
-  } else {
-    return [...commonColumns, ...weightColumns]; // 重量模式:公共列 + 重量列
-  }
-});
-
-// 4. 状态管理(保持不变)
-const waterData = ref([]);
-const loading = ref(true);
-const error = ref(null);
-const sortKey = ref('');
-const sortOrder = ref('asc');
-
-// 5. 数值格式化(体积数据保持原始格式)
-const formatValue = (value, isVolume,type) => {
-  if (value === null || value === undefined || value === '') return '-';
-  
-  if(type === 'number'){
-    const numValue = parseFloat(value);
-    if(isNaN(numValue)) 
-      return '-';
-    return isVolume ? numValue.toString():numValue;
-  }else{
-    return value;
-  }
-
-  const numValue = parseFloat(value);
-  if (isNaN(numValue)) return '-';
-  
-  // 体积数据保持原始格式,
-  return isVolume ? numValue.toString() : numValue;
-};
-
-// 6. 获取数据(保持不变)
-const fetchData = async () => {
-  try {
-    loading.value = true;
-    error.value = null;
-    const response = await axios.get('http://localhost:3000/table/Atmosphere_summary_data');
-    waterData.value = response.data.data || response.data;
-    console.log('原始数据字段名:', waterData.value[0] || '无数据'); 
-  } catch (err) {
-    error.value = err.message || '无法连接到服务器,请检查接口是否可用';
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 7. 过滤全空行(保持不变,但基于动态列过滤)
-const filteredData = computed(() => {
-  return waterData.value.filter(item => {
-    return displayColumns.value.some(col => {
-      const value = item[col.key];
-      // 字符串字段:只要非空就保留;数值字段:非空且非NaN
-      if (col.type === 'string') {
-        return value !== null && value !== '' && value !== '-';
-      } else {
-        return value !== null && value !== '' && value !== '-' && !isNaN(parseFloat(value));
-      }
-    });
-  });
-});
-
-// 8. 排序功能(保持不变,但只对当前显示的列生效)
-const sortedData = computed(() => {
-  if (!sortKey.value) return filteredData.value;
-  
-  return [...filteredData.value].sort((a, b) => {
-    const valA = a[sortKey.value];
-    const valB = b[sortKey.value];
-    // 处理非数值排序(如采样位置)
-    if (typeof valA === 'string' && typeof valB === 'string') {
-      return sortOrder.value === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA);
-    }
-    // 数值排序
-    const numA = parseFloat(valA) || -Infinity;
-    const numB = parseFloat(valB) || -Infinity;
-    if (numA < numB) return sortOrder.value === 'asc' ? -1 : 1;
-    if (numA > numB) return sortOrder.value === 'asc' ? 1 : -1;
-    return 0;
-  });
-});
-
-// 切换排序
-const sortData = (key) => {
-  if (sortKey.value === key) {
-    sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
-  } else {
-    sortKey.value = key;
-    sortOrder.value = 'asc';
-  }
-};
-
-//  组件挂载
-onMounted(() => {
-  fetchData();
-});
-</script>
-
-<style scoped>
-/* 样式保持不变 */
-</style>
-
-<style scoped>
-/* 布局 */
-.container {
-  width: 100%;
-  margin: 0 auto;
-  padding: 32px 16px;
-}
-.overflow-x-auto { overflow-x: auto; }
-
-/* 卡片 */
-.bg-white { background-color: #fff; }
-.rounded-xl { border-radius: 1rem; }
-.shadow-lg { 
-  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 
-             0 4px 6px -4px rgba(0,0,0,0.1); 
-}
-
-/* 文字 */
-.text-center { text-align: center; }
-.text-lg { font-size: 1.125rem; }
-.font-bold { font-weight: 700; }
-.text-gray-800 { color: #111827; }
-
-/* 动画 */
-.animate-spin {
-  animation: spin 1s linear infinite;
-}
-@keyframes spin {
-  from { transform: rotate(0deg); }
-  to { transform: rotate(360deg); }
-}
-
-/* 表格 */
-table { width: 100%; }
-.px-6 { padding: 0 1.5rem; }
-.py-4 { padding: 1rem 0; }
-.hover\:bg-gray-50:hover { background-color: #f9fafb; }
-
-/* 响应式 */
-@media (max-width: 640px) {
-  .container {
-     padding: 32px 8px; 
-     max-width: 1600px;
-    }
-  .px-6 { padding: 0 0.75rem; }
-}
-table {
-  border-collapse: collapse; /* 合并边框线 */
-}
-th, td {
-  border: 1px solid #d1d5db; /* 灰色边框 */
-  text-align: center; /* 内容居中 */
-  padding: 12px 8px; /* 内边距优化 */
-  min-width: 120px;
-}
-</style>

+ 0 - 218
src/views/User/HmOutFlux/atmosDeposition/atmcompanyline.vue

@@ -1,218 +0,0 @@
-<template>
-  <div class="container mx-auto px-4 py-8">
-    <div class="bg-white rounded-xl shadow-lg overflow-hidden">
-      <div class="p-6 border-b border-gray-200">
-        <h1 class="text-[clamp(1rem,3vw,2.5rem)] font-bold text-gray-800 text-center">大气污染公司列表</h1>
-      </div>
-      
-      <!-- 加载状态 -->
-      <div v-if="loading" class="py-20 flex justify-center items-center">
-        <div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
-      </div>
-      
-      <!-- 错误状态 -->
-      <div v-else-if="error" class="p-8 bg-red-50 border-l-4 border-red-400 text-red-700">
-        <div class="flex">
-          <div class="flex-shrink-0">
-            <i class="fa fa-exclamation-triangle text-red-500 text-xl"></i>
-          </div>
-          <div class="ml-3">
-            <h3 class="text-sm font-medium text-red-800">加载失败</h3>
-            <div class="mt-2 text-sm text-red-700">
-              <p>{{ error }}</p>
-            </div>
-            <div class="mt-4">
-              <button @click="fetchData" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150 ease-in-out">
-                <i class="fa fa-refresh mr-2"></i>重试
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-      
-      <!-- 数据表格 -->
-      <div v-else-if="filteredData.length > 0" class="overflow-x-auto">
-        <table class="min-w-full divide-y divide-gray-200">
-          <thead class="bg-gray-50">
-            <tr>
-              <th 
-                v-for="(col, index) in displayColumns" 
-                :key="index"
-                class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:bg-gray-100 transition-colors"
-                @click="sortData(col.key)"
-              >
-                <div class="flex items-center justify-between">
-                  {{ col.label }}
-                  <span v-if="sortKey === col.key" class="ml-1 text-gray-400">
-                    {{ sortOrder === 'asc' ? '↑' : '↓' }}
-                  </span>
-                </div>
-              </th>
-            </tr>
-          </thead>
-          <tbody class="bg-white divide-y divide-gray-200">
-            <tr v-for="(item, rowIndex) in sortedData" :key="rowIndex" 
-                class="hover:bg-gray-50 transition-colors duration-150">
-              <td 
-                v-for="(col, colIndex) in displayColumns" 
-                :key="colIndex"
-                class="px-6 py-4 whitespace-nowrap text-sm"
-              >
-                <div class="flex items-center">
-                  <div class="text-gray-900 font-medium">
-                    {{ item[col.key] !== null ? item[col.key] : '-' }}
-                  </div>
-                </div>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
-      
-      <!-- 空数据状态 -->
-      <div v-else class="p-8 text-center">
-        <div class="flex flex-col items-center justify-center">
-          <div class="text-gray-400 mb-4">
-            <i class="fa fa-database text-5xl"></i>
-          </div>
-          <h3 class="text-lg font-medium text-gray-900 mb-1">暂无有效数据</h3>
-          <p class="text-gray-500">已过滤全空行</p>
-        </div>
-      </div>
-  
-      <!-- 数据表格 + 统计 -->
-  <div class="p-4 bg-gray-50 border-t border-gray-200">
-    <div class="flex flex-col md:flex-row justify-between items-center">
-      <div class="text-sm text-gray-500 mb-2 md:mb-0">
-        共 <span class="font-medium text-gray-900">{{ filteredData.length }}</span> 条数据
-      </div>
-    </div>
-</div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, computed, onMounted } from 'vue';
-import axios from 'axios';
-
-// 定义固定列配置
-const displayColumns = ref([
-  { key: '污染源序号', label: '污染源序号' },
-  { key: '公司', label: '公司' },
-  { key: '类型', label: '类型' },
-  { key: '所属区县', label: '所属区县' },
-  { key: '大气颗粒物排放(t/a)', label: '大气颗粒物排放(t/a)' },
-  { key: '经度', label: '经度' },
-  { key: '纬度', label: '纬度' },
-]);
-
-// 状态管理
-const waterData = ref([]);
-const loading = ref(true);
-const error = ref(null);
-const sortKey = ref('');
-const sortOrder = ref('asc');
-
-// 获取数据
-const fetchData = async () => {
-  try {
-    loading.value = true;
-    error.value = null;
-    const response = await axios.get('http://localhost:3000/table/Atmosphere_company_data');
-    waterData.value = response.data.data || response.data;
-  } catch (err) {
-    error.value = err.message || '无法连接到服务器,请检查接口是否可用';
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 过滤全空行
-const filteredData = computed(() => {
-  return waterData.value.filter(item => {
-    return displayColumns.value.some(col => item[col.key] !== null && item[col.key] !== '-');
-  });
-});
-
-// 排序功能
-const sortedData = computed(() => {
-  if (!sortKey.value) return filteredData.value;
-  
-  return [...filteredData.value].sort((a, b) => {
-    const valA = a[sortKey.value];
-    const valB = b[sortKey.value];
-    if (valA < valB) return sortOrder.value === 'asc' ? -1 : 1;
-    if (valA > valB) return sortOrder.value === 'asc' ? 1 : -1;
-    return 0;
-  });
-});
-
-// 切换排序
-const sortData = (key) => {
-  if (sortKey.value === key) {
-    sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
-  } else {
-    sortKey.value = key;
-    sortOrder.value = 'asc';
-  }
-};
-
-// 组件挂载
-onMounted(() => {
-  fetchData();
-});
-</script>
-
-<style scoped>
-/* 布局 */
-.container {
-  max-width: 1280px;
-  margin: 0 auto;
-  padding: 32px 16px;
-}
-.overflow-x-auto { overflow-x: auto; }
-
-/* 卡片 */
-.bg-white { background-color: #fff; }
-.rounded-xl { border-radius: 1rem; }
-.shadow-lg { 
-  box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 
-             0 4px 6px -4px rgba(0,0,0,0.1); 
-}
-
-/* 文字 */
-.text-center { text-align: center; }
-.text-lg { font-size: 1.125rem; }
-.font-bold { font-weight: 700; }
-.text-gray-800 { color: #111827; }
-
-/* 动画 */
-.animate-spin {
-  animation: spin 1s linear infinite;
-}
-@keyframes spin {
-  from { transform: rotate(0deg); }
-  to { transform: rotate(360deg); }
-}
-
-/* 表格 */
-table { width: 100%; }
-.px-6 { padding: 0 1.5rem; }
-.py-4 { padding: 1rem 0; }
-.hover\:bg-gray-50:hover { background-color: #f9fafb; }
-
-/* 响应式 */
-@media (max-width: 640px) {
-  .container { padding: 32px 8px; }
-  .px-6 { padding: 0 0.75rem; }
-}
-table {
-  border-collapse: collapse; /* 合并边框线 */
-}
-th, td {
-  border: 1px solid #d1d5db; /* 灰色边框 */
-  text-align: center; /* 内容居中 */
-  padding: 12px 8px; /* 内边距优化 */
-}
-</style>

+ 0 - 366
src/views/User/HmOutFlux/atmosDeposition/atmsamplemap.vue

@@ -1,366 +0,0 @@
-<template>
-  <div class="map-wrapper">
-    <div ref="mapContainer" class="map-container"></div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, watch } from 'vue'; // 新增watch用于监听计算方式变化
-import L from 'leaflet';
-import 'leaflet/dist/leaflet.css';
-
-// 1. 接收父组件传递的计算方式(重量/体积)
-const props = defineProps({
-  calculationMethod: {
-    type: String,
-    required: true,
-    default: 'weight' // 默认按重量计算
-  }
-});
-
-const mapContainer = ref(null);
-const mapInstance = ref(null); // 保存地图实例
-const markers = ref([]); // 保存所有标记点实例及对应数据,用于后续更新弹窗
-
-// 2. 定义重量/体积对应的指标分类(核心:区分需要展示的字段)
-const metricsMap = {
-  weight: [ // 重量相关指标
-    { label: 'Cr mg/kg', key: 'Cr mg/kg' },
-    { label: 'As mg/kg', key: 'As mg/kg' },
-    { label: 'Cd mg/kg', key: 'Cd mg/kg' },
-    { label: 'Hg mg/kg', key: 'Hg mg/kg' },
-    { label: 'Pb mg/kg', key: 'Pb mg/kg' },
-    { label: '颗粒物重量 mg', key: '颗粒物的重量 mg' }
-  ],
-  volume: [ // 体积相关指标
-    { label: 'Cr ug/m³', key: 'Cr ug/m3' },
-    { label: 'As ug/m³', key: 'As ug/m3' },
-    { label: 'Cd ug/m³', key: 'Cd ug/m3' },
-    { label: 'Hg ug/m³', key: 'Hg ug/m3' },
-    { label: 'Pb ug/m³', key: 'Pb ug/m3' },
-    { label: '标准体积 m³', key: '标准体积 m3' },
-    { label: '颗粒物浓度 ug/m³', key: '颗粒物浓度ug/m3' },
-  ]
-};
-
-// 辅助函数:数值格式化
-function formatValue(value) {
-  if (value === undefined || value === null || value === '') return '未知';
-  return parseFloat(value);
-}
-
-// 辅助函数:位置格式化(处理"广东省韶关市"前缀)
-function formatLocation(fullLocation) {
-  if (!fullLocation) return '未知位置';
-  const processed = fullLocation.replace(/^广东省韶关市/, '').trim().replace(/^韶关市/, '');
-  return processed || '未知位置';
-}
-
-// 3. 生成弹窗内容(根据计算方式动态生成)
-function generatePopupContent(item, method) {
-  const metrics = metricsMap[method]; // 获取当前计算方式对应的指标
-  // 生成指标HTML片段
-  const metricsHtml = metrics.map(metric => `
-    <div class="data-item">
-      <span class="item-label">${metric.label}:</span>
-      <span class="item-value">${formatValue(item[metric.key])}</span>
-    </div>
-  `).join('');
-
-  // 弹窗整体结构(非指标信息保持不变)
-  return `
-    <div class="popup-container">
-      <div class="popup-header">
-        <h3 class="popup-title">${formatLocation(item.采样 || '未知采样点')}</h3>
-      </div>
-      <ul class="popup-info-list">
-        <li>
-          <span class="info-label">采样点ID:</span>
-          <span class="info-value">${item.样品名称 || '未知'}</span>
-        </li>
-      </ul>
-      <div class="grid-container">
-        <div class="grid-item">${metricsHtml}</div>
-      </div>
-    </div>
-  `;
-}
-
-onMounted(() => {
-  if (!mapContainer.value) {
-    console.error('❌ 地图容器未找到!');
-    return;
-  }
-
-  // 初始化地图
-  const map = L.map(mapContainer.value, {
-    center: [24.7, 114], // 韶关大致中心
-    zoom: 8.5,
-    minZoom: 8.3,
-  });
-  mapInstance.value = map;
-
-  // 区县颜色映射(保持不变)
-  const districtColorMap = {
-    "武江区": "#FF6B6B",
-    "浈江区": "#4ECDC4",
-    "曲江区": "#FFD166",
-    "始兴县": "#A0DAA9",
-    "仁化县": "#6A0572",
-    "翁源县": "#1A535C",
-    "乳源瑶族自治县": "#FF9F1C",
-    "新丰县": "#87CEEB",
-    "乐昌市": "#118AB2",
-    "南雄市": "#06D6A0",
-  };
-
-  // 加载区县边界(保持不变)
-  fetch('/data/韶关市各区县边界图.geojson')
-    .then(res => {
-      if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
-      return res.json();
-    })
-    .then(geojson => {
-      console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
-      
-      L.geoJSON(geojson, {
-        style: (feature) => {
-          const districtName = feature.properties.name;
-          const color = districtColorMap[districtName] || '#cccccc';
-          return {
-            fillColor: color,
-            fillOpacity: 0.7,
-            color: '#333333',
-            weight: 2,
-          };
-        },
-      }).addTo(map);
-
-       fetch('/data/乐昌市.geoJson') // 假设文件路径为 /data/乐昌市.geojson
-        .then(res => {
-          if (!res.ok) throw new Error(`乐昌市边界加载失败:${res.status}`);
-          return res.json();
-        })
-        .then(lechangGeojson => {
-          console.log('✅ 乐昌市边界数据加载完成', lechangGeojson);
-          
-          // 为乐昌市设置更突出的样式(与原有边界区分)
-          L.geoJSON(lechangGeojson, {
-            style: () => {
-              return {
-                fillColor: districtColorMap["乐昌市"], // 复用原有颜色
-                fillOpacity: 0.5, // 透明度略低,避免覆盖原有边界
-                color: '#000000', // 边框颜色加深
-                weight: 4, // 边框加粗,突出显示
-                dashArray: '5, 5', // 可选:添加虚线效果,进一步区分
-              };
-            },
-          }).addTo(map);
-        })
-        .catch(err => {
-          console.warn('⚠️ 乐昌市边界加载失败(不影响主地图):', err);
-        });
-
-      // 加载大气数据并创建标记点
-      fetch('http://localhost:3000/table/Atmosphere_summary_data')
-        .then(res => {
-          if (!res.ok) throw new Error(`大气数据加载失败:${res.status}`);
-          return res.json();
-        })
-        .then(atmosphereData => {
-          console.log('✅ 大气数据加载完成,记录数:', atmosphereData.length);
-          markers.value = []; // 清空标记点数组
-          
-          atmosphereData.forEach((item, idx) => {
-            try {
-              // 提取经纬度(保持不变)
-              const latField = ['latitude', 'lat', '纬度'].find(key => item[key] !== undefined);
-              const lngField = ['longitude', 'lng', '经度'].find(key => item[key] !== undefined);
-              
-              if (!latField || !lngField) {
-                console.error(`❌ 未找到经纬度字段(第${idx}条)`);
-                return;
-              }
-              
-              // 清理经纬度数据(保持不变)
-              const cleanLat = String(item[latField]).replace(/[^\d.-]/g, '');
-              const cleanLng = String(item[lngField]).replace(/[^\d.-]/g, '');
-              
-              const lat = parseFloat(parseFloat(cleanLat).toFixed(6));
-              const lng = parseFloat(parseFloat(cleanLng).toFixed(6));
-              
-              // 坐标范围校验(保持不变)
-              if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
-                console.warn(`❌ 坐标超出范围(第${idx}条):`, lat, lng);
-                return;
-              }
-              
-              // 创建标记点(保持不变)
-              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);
-            }
-          });
-
-          console.log(`✅ 成功创建 ${markers.value.length} 个大气数据标记点`);
-        })
-        .catch(err => {
-          console.error('❌ 大气数据加载失败:', err);
-          alert('大气数据接口错误:' + err.message);
-        });
-    })
-    .catch(err => {
-      console.error('❌ 区县边界加载失败:', err);
-      alert('区县边界加载错误:' + err.message);
-    });
-});
-
-// 4. 监听计算方式变化,更新所有标记点的弹窗内容
-watch(
-  () => props.calculationMethod,
-  (newMethod) => {
-    markers.value.forEach(({ marker, item }) => {
-      // 重新绑定弹窗内容(使用新的计算方式)
-      marker.bindPopup(generatePopupContent(item, newMethod));
-    });
-    console.log(`✅ 已切换为${newMethod === 'weight' ? '重量' : '体积'}计算方式,弹窗内容已更新`);
-  }
-);
-</script>
-
-<style scoped>
-/* 样式保持不变,仅需确保弹窗内容布局适配单组数据 */
-.map-wrapper {
-  width: 100%;
-  height: 100%;
-  position: relative;
-}
-.map-container {
-  width: 100% !important;
-  height: 100% !important;
-}
-
-/* 弹窗样式 */
-::v-deep .leaflet-popup-content-wrapper {
-  padding: 0 !important;
-  border-radius: 10px !important;
-  box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
-}
-
-::v-deep .leaflet-popup-content {
-  margin: 0 !important;
-  width: auto !important;
-  max-width: 300px; /* 适当减小最大宽度,适配单列布局 */
-}
-
-::v-deep .popup-container {
-  min-width: 280px;
-  padding: 12px;
-  font-family: "Microsoft YaHei", sans-serif;
-}
-
-::v-deep .popup-header {
-  margin-bottom: 10px;
-}
-
-::v-deep .popup-title {
-  text-align: center;
-  font-size: 16px;
-  font-weight: 700;
-  color: #0066CC;
-  margin: 0 0 5px;
-  padding-bottom: 6px;
-  border-bottom: 1.5px solid #0066CC;
-}
-
-::v-deep .popup-info-list {
-  list-style: none;
-  padding: 0;
-  margin: 0 0 10px;
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 6px;
-}
-
-::v-deep .popup-info-list li {
-  display: flex;
-  margin: 0;
-  padding: 3px 6px;
-  background: #f9f9f9;
-  border-radius: 3px;
-}
-
-::v-deep .info-label {
-  flex: 0 0 85px;
-  font-weight: 600;
-  color: #333;
-  font-size: 13px;
-}
-
-::v-deep .info-value {
-  flex: 1;
-  color: #666;
-  font-size: 13px;
-   white-space: nowrap;
-}
-
-::v-deep .grid-container {
-  display: grid;
-  grid-template-columns: 1fr; /* 改为单列布局,适配分类后的指标 */
-  gap: 6px;
-}
-
-::v-deep .grid-item {
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
-}
-
-::v-deep .data-item {
-  display: flex;
-  justify-content: space-between;
-  padding: 6px 8px;
-  background: #f9f9f9;
-  border-radius: 3px;
-}
-
-::v-deep .item-label {
-  font-weight: 600;
-  color: #555;
-  font-size: 13px;
-}
-
-::v-deep .item-value {
-  color: #000;
-  font-size: 13px;
-}
-
-/* 隐藏弹窗箭头 */
-::v-deep .leaflet-popup-tip {
-  display: none;
-}
-
-/* 标记点样式 */
-::v-deep .leaflet-circle-marker {
-  stroke-width: 1.5px !important;
-}
-
-/* 大气数据标记点的悬停效果 */
-::v-deep .leaflet-marker-pane .leaflet-circle-marker[fill="#118AB2"]:hover {
-  fill-opacity: 1 !important;
-  stroke-width: 2.5px !important;
-}
-</style>

+ 5 - 8
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue

@@ -23,12 +23,9 @@
 <!--污染企业-->
 
 <script setup>
-import { ref, onMounted, watch, computed } from 'vue';
-import * as echarts from 'echarts';
-import atmcompanyline from './atmcompanyline.vue';
-import atmcompanymap from './atmcompanymap.vue';
-import AtmCompanytencentMap from './atmCompanytencentMap.vue';
-import HeavyMetalEnterprisechart from './heavyMetalEnterprisechart.vue';
+import atmcompanyline from '@/components/atmpollution/atmcompanyline.vue';
+import atmcompanymap from '@/components/atmpollution/atmcompanymap.vue';
+import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnterprisechart.vue';
 
 
 
@@ -51,7 +48,7 @@ import HeavyMetalEnterprisechart from './heavyMetalEnterprisechart.vue';
 
 .map-holder {
   position: relative ;
-  height: 600px;
+  height: 450px;
   z-index: 100; /* 保持地图在中间层级 */
   overflow: visible; /* 允许内容溢出 */
 }
@@ -90,7 +87,7 @@ import HeavyMetalEnterprisechart from './heavyMetalEnterprisechart.vue';
   position: relative;      /* 为伪元素定位做准备 */
 
   /* 文字样式:简约但醒目 */
-  font-size: 1.7rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
+  font-size: 1.5rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
   font-weight: 600;        /* 适度加粗,比正文突出但不夸张 */
   color: #1e88e5;          /* 统一蓝色,和方块颜色呼应 */
   line-height: 1.2;        /* 紧凑行高,避免臃肿 */

+ 0 - 250
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprisechart.vue

@@ -1,250 +0,0 @@
-<template>
-  <div class="region-average-chart">
-    <!-- 图表容器 -->
-    <div ref="chartRef" class="chart-box"></div>
-    <!-- 加载/错误提示 -->
-    <div v-if="loading" class="status">数据加载中...</div>
-    <div v-else-if="error" class="status error">{{ error }}</div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted } from 'vue';
-import * as echarts from 'echarts';
-import axios from 'axios';
-import { icon } from 'leaflet';
-
-// ========== 1. 配置项(根据实际需求修改) ==========
-const POLLUTANT_API = 'http://localhost:3000/table/Atmosphere_company_data'; 
-
-// 韶关市区县白名单(确保与接口返回的“所属区县”完全一致)
-const SG_REGIONS = [
-  '浈江区', '武江区', '曲江区', '乐昌市', 
-  '南雄市', '始兴县', '仁化县', '翁源县', 
-  '新丰县', '乳源县'
-];
-
-// 排除的非污染物字段(根据接口结构调整)
-const EXCLUDE_FIELDS = [
-  '污染源序号', '公司', '类型', '经度', '纬度' // 这些字段不是污染物,需排除
-];
-
-// ========== 2. 响应式数据 & 变量 ==========
-const chartRef = ref(null);   // ECharts容器引用
-const loading = ref(true);    // 加载状态
-const error = ref('');        // 错误信息
-let myChart = null;          // ECharts实例
-
-// ========== 3. 数据处理核心逻辑 ==========
-const processData = (apiData) => {
-  // 3.1 空数据保护
-  if (!apiData || apiData.length === 0) {
-    console.error('接口返回空数据');
-    return { regions: [], series: [], totalSamples: 0 };
-  }
-
-  // 3.2 提取**污染物字段**(自动过滤非数值字段)
-  const pollutantFields = Object.keys(apiData[0])
-    .filter(key => 
-      !EXCLUDE_FIELDS.includes(key) &&  // 排除非污染物字段
-      !isNaN(parseFloat(apiData[0][key])) // 只保留数值字段
-    );
-
-  // 3.3 无有效污染物字段的保护
-  if (pollutantFields.length === 0) {
-    console.error('未识别到有效污染物字段,请检查 EXCLUDE_FIELDS 配置');
-    return { regions: [], series: [], totalSamples: 0 };
-  }
-
-  // 3.4 按区县分组统计(总和 + 计数)
-  const regionGroups = {}; // 结构:{ 区县: { 污染物: { sum, count } } }
-  const globalStats={};//全局统计对象
-  let totalSamples = 0;    // 总样本数
-
-  pollutantFields.forEach(field=>{
-    globalStats[field] = {sum:0,count:0};
-  })
-
-  apiData.forEach(item => {
-    const region = item['所属区县'] || '未知区县'; // 提取区县
-    totalSamples++;
-
-    // 初始化区县分组(每个污染物字段都要初始化)
-    if (!regionGroups[region]) {
-      regionGroups[region] = {};
-      pollutantFields.forEach(field => {
-        regionGroups[region][field] = { sum: 0, count: 0 };
-      });
-    }
-
-    // 累加每个污染物的浓度
-    pollutantFields.forEach(field => {
-      const value = parseFloat(item[field]);
-      if (!isNaN(value)) { // 过滤无效值(如空字符串、null)
-        regionGroups[region][field].sum += value;
-        regionGroups[region][field].count++;
-
-        globalStats[field].sum+=value;
-        globalStats[field].count++;
-      }
-    });
-  });
-
-  // 3.5 筛选**有效区县**(仅韶关市区县白名单内的区域)
-  const validRegions = SG_REGIONS.filter(region => 
-    regionGroups[region] !== undefined
-  );
-  validRegions.push('全部样本平均');//全局平均的分类
-
-  // 3.6 构建ECharts所需的series数据
-  const series = pollutantFields.map((field, index) => ({
-    name: field, // 污染物名称作为系列名
-    type: 'bar',
-    data: validRegions.map(region => {
-        if(region === '全部样本平均'){
-            const stats = globalStats[field];
-            return stats.count>0
-             ?(stats.sum/stats.count).toFixed(2)
-             :0;
-        }
-      const group = regionGroups[region][field];
-      return group.count > 0 
-        ? (group.sum / group.count).toFixed(2) // 计算平均值,保留2位小数
-        : 0; // 无数据时显示0
-    }),
-    itemStyle: { color: (params)=>{
-      return params.name  ==='全部样本平均'?'#ff0000' : '#1890ff'
-    } }, 
-    label: {
-      show: true,
-      position: 'top',
-      fontSize: 18,
-      color: '#333'
-    }
-  }));
-
-  return { regions: validRegions, series, totalSamples };
-};
-
-// ========== 4. ECharts 初始化 & 更新 ==========
-const initChart = ({ regions, series, totalSamples }) => {
-  if (!chartRef.value) return;
-
-  // 销毁旧实例(避免重复初始化)
-  if (myChart && !myChart.isDisposed()) {
-    myChart.dispose();
-  }
-
-  myChart = echarts.init(chartRef.value);
-  const option = {
-    title: {
-      left: 'center',
-      subtext: `(基于 ${totalSamples} 个有效样本计算)`,
-      subtextStyle: { fontSize: 14, color: '#666' }
-    },
-    tooltip: {
-      trigger: 'axis',
-      formatter: (params) => {
-        const region = params[0].name;
-        let content = `${region}:<br>`;
-        params.forEach(p => {
-          content += `${p.seriesName}: ${p.value} t/a<br>`;
-        });
-        return content;
-      },
-      textStyle: { fontSize: 16 }
-    },
-    xAxis: {
-      type: 'category',
-      data: regions,
-      axisLabel: {
-        formatter: val => val.replace('韶关市', ''), // 简化显示(可选)
-        fontSize: 16,
-        interval:0
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: '浓度 (t/a)',
-      nameTextStyle: { fontSize: 16 },
-      axisLabel: { fontSize: 16 }
-    },
-    legend: {
-      data: series.map(s => s.name),
-      bottom: 10,
-      textStyle: { fontSize: 14 },
-      icon:'none'
-    },
-    series:series,
-    grid: { left: '5%', right: '5%', bottom: '15%', containLabel: true }
-  };
-
-  myChart.setOption(option);
-};
-
-// ========== 5. 生命周期 & 响应式 ==========
-onMounted(async () => {
-  loading.value = true;
-  try {
-    // 1. 请求接口数据
-    const res = await axios.get(POLLUTANT_API, { timeout: 10000 });
-    
-    // 2. 处理数据
-    const processedData = processData(res.data);
-    
-    // 3. 渲染图表
-    initChart(processedData);
-
-  } catch (err) {
-    // 细化错误提示(网络/服务器/超时等)
-    if (err.response) {
-      error.value = `数据加载失败(${err.response.status})`;
-    } else if (err.request) {
-      error.value = '网络错误,无法连接服务器';
-    } else {
-      error.value = `加载失败:${err.message}`;
-    }
-    console.error('接口请求错误:', err);
-  } finally {
-    loading.value = false;
-  }
-});
-
-// 窗口resize时自动调整图表
-const resizeHandler = () => {
-  if (myChart) myChart.resize();
-};
-onMounted(() => window.addEventListener('resize', resizeHandler));
-onUnmounted(() => window.removeEventListener('resize', resizeHandler));
-</script>
-
-<style scoped>
-.region-average-chart {
-  width: 100%;
-  max-width: 1200px;
-  margin: 20px auto;
-  position: relative;
-}
-.chart-box {
-  width: 100%;
-  height: 600px;
-  min-height: 400px;
-  background: white;
-  border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-}
-.status {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  padding: 12px 20px;
-  background: rgba(255,255,255,0.9);
-  border-radius: 6px;
-  font-size: 16px;
-}
-.error { 
-  color: #ff4d4f;
-  font-weight: bold;
-}
-</style>

+ 0 - 25
src/views/User/HmOutFlux/atmosDeposition/samplingDesc3.vue

@@ -1,25 +0,0 @@
-<template>
-  <div class="">
-    
-  </div>
-</template>
-
-<script>
-export default {
-  name: '',
-  data() {
-    return {
-      
-    };
-  },
-  methods: {
-    
-  }
-};
-</script>
-
-<style scoped>
-  . {
-    
-  }
-</style>

+ 5 - 5
src/views/User/HmOutFlux/irrigationWater/crossSection.vue

@@ -16,10 +16,10 @@
 </template>
 
 <script setup lang="ts">
-import CrossSectionSamplelineData from './crossSectionSamplelineData.vue';
-import CrossSetionData1 from './crossSetionData1.vue';
-import CrossSetionData2 from './crossSetionData2.vue';
-import crosssectionmap from './crosssectionmap.vue';
+import CrossSectionSamplelineData from '@/components/irrpollution/crossSectionSamplelineData.vue'
+import CrossSetionData1 from '@/components/irrpollution/crossSetionData1.vue';
+import CrossSetionData2 from '@/components/irrpollution/crossSetionData2.vue';
+import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 
 </script>
 
@@ -43,7 +43,7 @@ import crosssectionmap from './crosssectionmap.vue';
   z-index: 200;
 
   /* 文字样式:简约但醒目 */
-  font-size: 1.7rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
+  font-size: 1.5rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
   font-weight: 600;        /* 适度加粗,比正文突出但不夸张 */
   color: #1e88e5;          /* 统一蓝色,和方块颜色呼应 */
   line-height: 1.2;        /* 紧凑行高,避免臃肿 */

+ 0 - 139
src/views/User/HmOutFlux/irrigationWater/crossSectionSamplelineData.vue

@@ -1,139 +0,0 @@
-<template>
-  <div class="map-page">
-    <!-- 数据表格容器 -->
-    <div class="table-container">
-      <table class="data-table">
-        <!-- 表头 -->
-        <thead>
-          <tr>
-            <th>断面编号</th>
-            <th>所属河流</th>
-            <th>断面位置</th>
-            <th>所属区县</th>
-            <th>Cd含量(ug/L)</th>
-            <th>经度</th>
-            <th>纬度</th>
-          </tr>
-        </thead>
-        <!-- 表体(遍历数据) -->
-        <tbody>
-          <tr v-for="item in state.excelData" :key="item.id">
-            <td>{{ item.id }}</td>
-            <td>{{ item.river }}</td>
-            <td>{{ item.location }}</td>
-            <td>{{ item.district }}</td>
-            <td>{{ item.cdValue }}</td>
-            <td>{{ item.longitude.toFixed(6) }}</td> <!-- 保留6位小数 -->
-            <td>{{ item.latitude.toFixed(6) }}</td>
-          </tr>
-        </tbody>
-      </table>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
-import { wgs84togcj02 } from 'coordtransform';
-
-// 状态管理
-const error = ref(null)
-const state = reactive({
-  excelData: [], // 存储解析后的断面数据
-  riverAvgData:[],//存储按河流分组后的平均数据
-})
-
-// 初始化断面数据(直接嵌入你的Excel数据)
-const initData = () => {
-  const rawData = [
-    { "断面编号": 0, "所属河流": "浈江", "断面位置": "小古录", "所属区县": "始兴县", "经度": 114.208543, "纬度": 25.059851, "Cd(ug/L)": 0.11 },
-    { "断面编号": 1, "所属河流": "浈江", "断面位置": "长坝", "所属区县": "仁化县", "经度": 113.692874, "纬度": 24.874845, "Cd(ug/L)": 1.116 },
-    { "断面编号": 2, "所属河流": "浈江", "断面位置": "东河桥", "所属区县": "浈江区", "经度": 113.601631, "纬度": 24.80784, "Cd(ug/L)": 3.46 },
-    { "断面编号": 3, "所属河流": "武江", "断面位置": "坪石", "所属区县": "乐昌市", "经度": 113.066281, "纬度": 25.274421, "Cd(ug/L)": 0.98 },
-    { "断面编号": 4, "所属河流": "武江", "断面位置": "乐昌", "所属区县": "乐昌市", "经度": 113.338782, "纬度": 25.129212, "Cd(ug/L)": 0.11 },
-    { "断面编号": 5, "所属河流": "武江", "断面位置": "武江桥", "所属区县": "乐昌市", "经度": 113.349815, "纬度": 25.120278, "Cd(ug/L)": 0.15 },
-    { "断面编号": 6, "所属河流": "北江", "断面位置": "九公里", "所属区县": "浈江区", "经度": 113.580758, "纬度": 24.761299, "Cd(ug/L)": 7.83 },
-    { "断面编号": 7, "所属河流": "北江", "断面位置": "白土", "所属区县": "曲江区", "经度": 113.531284, "纬度": 24.679958, "Cd(ug/L)": 5.94 },
-    { "断面编号": 8, "所属河流": "浈江", "断面位置": "昆仑水站", "所属区县": "南雄市", "经度": 114.3629285, "纬度": 25.10053746, "Cd(ug/L)": 0.517 },
-    { "断面编号": 9, "所属河流": "北江", "断面位置": "白沙", "所属区县": "曲江", "经度": 113.5707136, "纬度": 24.58139261, "Cd(ug/L)": 1.54 },
-    { "断面编号": 10, "所属河流": "浈江", "断面位置": "周田水站", "所属区县": "仁化县", "经度": 113.8293461, "纬度": 24.97851516, "Cd(ug/L)": 0.182 },
-    { "断面编号": 11, "所属河流": "武江", "断面位置": "坪石水站", "所属区县": "乐昌市", "经度": 113.0467854, "纬度": 25.28883459, "Cd(ug/L)": 1.071 }
-  ];
-
-  // 处理坐标(WGS84转GCJ02,腾讯地图用GCJ02)
-  state.excelData = rawData.map(item => {
-    const lng = Number(item.经度);
-    const lat = Number(item.纬度);
-    if (isNaN(lat) || isNaN(lng)) {
-      console.error('无效经纬度:', item);
-      return null;
-    }
-    const [gcjLng, gcjLat] = wgs84togcj02(lng, lat); // 坐标转换
-    return {
-      id: item.断面编号,
-      river: item.所属河流,
-      location: item.断面位置,
-      district: item.所属区县,
-      cdValue: item["Cd(ug/L)"],
-      latitude: gcjLat,
-      longitude: gcjLng,
-    };
-  }).filter(item => item !== null);
-}
-
-
-
-// 生命周期
-onMounted(async () => {
-  try {
-    initData();
-  } catch (err) {
-    error.value = err.message;
-  }
-})
-
-</script>
-
-<style scoped>
-.map-page, .chart-page {
-  width: 100%;
-  margin: 0 auto 24px; /* 水平居中,底部间距24px */
-  background-color: white;
-  border-radius: 12px;
-  padding: 20px;
-  box-sizing: border-box;
-}
-
-
-.data-table {
-  width: 100%;
-  border-collapse: collapse;/*合并边框 */
-  min-width: 800px;
-}
-
-.data-table th,.data-table td {
-  padding: 12px 15px;
-  text-align: center;
-  border: 1px solid #e5e7eb;
-}
-
-.data-table th {
-  background-color: #f3f4f6;
-  font-weight: bold;
-  color: #1f2937;
-}
-
-.data-table tr:nth-child(even){
-  background-color: #f9fafb;
-}
-
-.data-table tr:hover{
-  background-color: #f3f4f6;
-}
-/* 响应式调整 */
-@media (max-width: 768px) {
-  .map-page {
-    width: 96%; /* 小屏幕稍窄,避免边缘拥挤 */
-  }
-}
-</style>

+ 0 - 217
src/views/User/HmOutFlux/irrigationWater/crossSetionData1.vue

@@ -1,217 +0,0 @@
-<template>
-    <!--柱状图容器-->
-  <div class="chart-page">
-    <div 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'
-
-// 状态管理
-const error = ref(null)
-const chartContainer=ref(null)
-let chart = null
-const state = reactive({
-  excelData: [], // 存储解析后的断面数据
-  riverAvgData:[],//存储按河流分组后的平均数据
-})
-
-// 初始化断面数据(直接嵌入你的Excel数据)
-const initData = () => {
-  const rawData = [
-    { "断面编号": 0, "所属河流": "浈江", "断面位置": "小古录", "所属区县": "始兴县", "经度": 114.208543, "纬度": 25.059851, "Cd(ug/L)": 0.11 },
-    { "断面编号": 1, "所属河流": "浈江", "断面位置": "长坝", "所属区县": "仁化县", "经度": 113.692874, "纬度": 24.874845, "Cd(ug/L)": 1.116 },
-    { "断面编号": 2, "所属河流": "浈江", "断面位置": "东河桥", "所属区县": "浈江区", "经度": 113.601631, "纬度": 24.80784, "Cd(ug/L)": 3.46 },
-    { "断面编号": 3, "所属河流": "武江", "断面位置": "坪石", "所属区县": "乐昌市", "经度": 113.066281, "纬度": 25.274421, "Cd(ug/L)": 0.98 },
-    { "断面编号": 4, "所属河流": "武江", "断面位置": "乐昌", "所属区县": "乐昌市", "经度": 113.338782, "纬度": 25.129212, "Cd(ug/L)": 0.11 },
-    { "断面编号": 5, "所属河流": "武江", "断面位置": "武江桥", "所属区县": "乐昌市", "经度": 113.349815, "纬度": 25.120278, "Cd(ug/L)": 0.15 },
-    { "断面编号": 6, "所属河流": "北江", "断面位置": "九公里", "所属区县": "浈江区", "经度": 113.580758, "纬度": 24.761299, "Cd(ug/L)": 7.83 },
-    { "断面编号": 7, "所属河流": "北江", "断面位置": "白土", "所属区县": "曲江区", "经度": 113.531284, "纬度": 24.679958, "Cd(ug/L)": 5.94 },
-    { "断面编号": 8, "所属河流": "浈江", "断面位置": "昆仑水站", "所属区县": "南雄市", "经度": 114.3629285, "纬度": 25.10053746, "Cd(ug/L)": 0.517 },
-    { "断面编号": 9, "所属河流": "北江", "断面位置": "白沙", "所属区县": "曲江", "经度": 113.5707136, "纬度": 24.58139261, "Cd(ug/L)": 1.54 },
-    { "断面编号": 10, "所属河流": "浈江", "断面位置": "周田水站", "所属区县": "仁化县", "经度": 113.8293461, "纬度": 24.97851516, "Cd(ug/L)": 0.182 },
-    { "断面编号": 11, "所属河流": "武江", "断面位置": "坪石水站", "所属区县": "乐昌市", "经度": 113.0467854, "纬度": 25.28883459, "Cd(ug/L)": 1.071 }
-  ];
-
-  // 处理坐标(WGS84转GCJ02,腾讯地图用GCJ02)
-  state.excelData = rawData.map(item => {
-    const lng = Number(item.经度);
-    const lat = Number(item.纬度);
-    if (isNaN(lat) || isNaN(lng)) {
-      console.error('无效经纬度:', item);
-      return null;
-    }
-    const [gcjLng, gcjLat] = wgs84togcj02(lng, lat); // 坐标转换
-    return {
-      id: item.断面编号,
-      river: item.所属河流,
-      location: item.断面位置,
-      district: item.所属区县,
-      cdValue: item["Cd(ug/L)"],
-      latitude: gcjLat,
-      longitude: gcjLng,
-    };
-  }).filter(item => item !== null);
-  calculateRiverAvg();
-}
-
-const calculateRiverAvg = () => {
-  // 按河流分组
-  const riverGroups = {};
-  
-  // 分组并累加浓度值
-  state.excelData.forEach(item => {
-    if (!riverGroups[item.river]) {
-      riverGroups[item.river] = {
-        total: 0,
-        count: 0,
-        avg: 0
-      };
-    }
-    riverGroups[item.river].total += item.cdValue;
-    riverGroups[item.river].count += 1;
-  });
-  
-  // 计算每组平均值
-  const riverAvg = [];
-  let totalAll = 0;
-  let countAll = 0;
-  
-  for (const river in riverGroups) {
-    const avg = riverGroups[river].total / riverGroups[river].count;
-    riverAvg.push({
-      river,
-      avg: parseFloat(avg.toFixed(3)) // 保留3位小数
-    });
-    totalAll += riverGroups[river].total;
-    countAll += riverGroups[river].count;
-  }
-  
-  // 计算总平均值并添加到数组
-  const totalAvg = {
-    river: '总河流平均',
-    avg: parseFloat((totalAll / countAll).toFixed(3))
-  };
-  
-  riverAvg.push(totalAvg);
-  state.riverAvgData = riverAvg;
-  
-  // 更新图表
-  updateChart();
-}
-
-// 新增:初始化图表
-const initChart = () => {
-  if (chartContainer.value) {
-    chart = echarts.init(chartContainer.value);
-    updateChart();
-  }
-}
-
-// 新增:更新图表数据
-const updateChart = () => {
-  if (!chart || state.riverAvgData.length === 0) return;
-  
-  // 准备图表数据
-  const rivers = state.riverAvgData.map(item => item.river);
-  const avgs = state.riverAvgData.map(item => item.avg);
-  
-  // 配置图表选项
-  const option = {
-    tooltip: {
-      trigger: 'axis',
-      axisPointer: {
-        type: 'shadow'
-      },
-      formatter: '{a} <br/>{b}: {c} ug/L'
-    },
-    grid: {
-      //left: '3%',
-      right: '4%',
-      bottom: '3%',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: rivers,
-      axisLabel: {
-        interval: 0,
-        fontSize:18
-      }
-    },
-    yAxis: {
-      type: 'value',
-      name: 'Cd浓度 (ug/L)',
-      min: 0,
-      nameTextStyle:{
-        fontSize:18,
-      },
-      axisLabel: {
-        formatter: '{value}',
-        fontSize:18
-      }
-    },
-    series: [
-      {
-        name: '平均镉浓度',
-        type: 'bar',
-        data: avgs,
-        itemStyle: {
-          // 为总平均值设置不同颜色
-          color: function(params) {
-            return params.dataIndex === rivers.length - 1 ? '#FF4500' : '#1E88E5';
-          }
-        },
-        label: {
-          show: true,
-          position: 'top',
-          formatter: '{c}',
-          fontSize:18
-        },
-        emphasis: {
-          focus: 'series'
-        }
-      }
-    ]
-  };
-  
-  // 设置图表选项
-  chart.setOption(option);
-}
-
-// 生命周期
-onMounted(async () => {
-  try {
-    initData();
-    initChart();
-    //监听窗口大小变化,调整图表
-    window.addEventListener('resize',()=>{
-      if(chart){
-        chart.resize();
-      }
-    })
-  } catch (err) {
-    error.value = err.message;
-  }
-})
-
-onBeforeUnmount(() => {
-  if(chart){
-    chart.dispose();
-  }
-})
-</script>
-
-<style>
-
-/* 图表容器样式 */
-.chart-container {
-  width: 100%; /* 占满图表容器宽度 */
-  height: 400px;
-  margin: 0 auto;
-  border-radius: 12px;
-}
-</style>

+ 0 - 272
src/views/User/HmOutFlux/irrigationWater/crosssectionmap.vue

@@ -1,272 +0,0 @@
-<template>
-  <div class="map-wrapper">
-    <div ref="mapContainer" class="map-container"></div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue';
-import L from 'leaflet';
-import 'leaflet/dist/leaflet.css';
-
-const mapContainer = ref(null);
-
-// 本地数据定义
-const rawData = [
-  { "断面编号": 0, "所属河流": "浈江", "断面位置": "小古录", "所属区县": "始兴县", "经度": 114.208543, "纬度": 25.059851, "Cd(ug/L)": 0.11 },
-  { "断面编号": 1, "所属河流": "浈江", "断面位置": "长坝", "所属区县": "仁化县", "经度": 113.692874, "纬度": 24.874845, "Cd(ug/L)": 1.116 },
-  { "断面编号": 2, "所属河流": "浈江", "断面位置": "东河桥", "所属区县": "浈江区", "经度": 113.601631, "纬度": 24.80784, "Cd(ug/L)": 3.46 },
-  { "断面编号": 3, "所属河流": "武江", "断面位置": "坪石", "所属区县": "乐昌市", "经度": 113.066281, "纬度": 25.274421, "Cd(ug/L)": 0.98 },
-  { "断面编号": 4, "所属河流": "武江", "断面位置": "乐昌", "所属区县": "乐昌市", "经度": 113.338782, "纬度": 25.129212, "Cd(ug/L)": 0.11 },
-  { "断面编号": 5, "所属河流": "武江", "断面位置": "武江桥", "所属区县": "乐昌市", "经度": 113.349815, "纬度": 25.120278, "Cd(ug/L)": 0.15 },
-  { "断面编号": 6, "所属河流": "北江", "断面位置": "九公里", "所属区县": "浈江区", "经度": 113.580758, "纬度": 24.761299, "Cd(ug/L)": 7.83 },
-  { "断面编号": 7, "所属河流": "北江", "断面位置": "白土", "所属区县": "曲江区", "经度": 113.531284, "纬度": 24.679958, "Cd(ug/L)": 5.94 },
-  { "断面编号": 8, "所属河流": "浈江", "断面位置": "昆仑水站", "所属区县": "南雄市", "经度": 114.3629285, "纬度": 25.10053746, "Cd(ug/L)": 0.517 },
-  { "断面编号": 9, "所属河流": "北江", "断面位置": "白沙", "所属区县": "曲江", "经度": 113.5707136, "纬度": 24.58139261, "Cd(ug/L)": 1.54 },
-  { "断面编号": 10, "所属河流": "浈江", "断面位置": "周田水站", "所属区县": "仁化县", "经度": 113.8293461, "纬度": 24.97851516, "Cd(ug/L)": 0.182 },
-  { "断面编号": 11, "所属河流": "武江", "断面位置": "坪石水站", "所属区县": "乐昌市", "经度": 113.0467854, "纬度": 25.28883459, "Cd(ug/L)": 1.071 }
-];
-
-// 定义蓝色三角形标记
-const blueTriangle = L.divIcon({
-  className: 'custom-div-icon',
-  html: `<svg width="24" height="24" viewBox="0 0 24 24">
-          <path d="M12 2L2 22h20L12 2z" fill="#0066CC" stroke="#003366" stroke-width="2"/>
-        </svg>`,
-  iconSize: [24, 24],
-  iconAnchor: [12, 24]
-});
-
-onMounted(() => {
-  // 初始化地图
-  if (!mapContainer.value) {
-    console.error('❌ 地图容器未找到!');
-    return;
-  }
-
-  const map = L.map(mapContainer.value, {
-    center: [24.75, 114], // 韶关大致中心  前大往下,后大往左
-    zoom: 8.5,
-    minZoom:8.3,
-  });
-
-  // 区县颜色映射(增强匹配)
-  const districtColorMap = {
-    "武江区": "#FF6B6B",
-    "浈江区": "#4ECDC4",
-    "曲江区": "#FFD166",
-    "始兴县": "#A0DAA9",
-    "仁化县": "#6A0572",
-    "翁源县": "#1A535C",
-    "乳源瑶族自治县": "#FF9F1C",
-    "新丰县": "#87CEEB",
-    "乐昌市": "#118AB2",
-    "南雄市": "#06D6A0",
-    "韶关市": "#cccccc",
-  };
-
-  // 增强匹配函数
-  function getDistrictColor(name) {
-    // 精确匹配
-    if (districtColorMap[name]) return districtColorMap[name];
-    
-    // 模糊匹配(尝试添加/移除"市"/"县"/"区"/"自治县")
-    const normalizedName = name.replace(/市|县|区|自治县/g, '');
-    for (const key in districtColorMap) {
-      if (key.includes(normalizedName) || normalizedName.includes(key.replace(/市|县|区|自治县/g, ''))) {
-        return districtColorMap[key];
-      }
-    }
-    
-    return '#cccccc'; // 默认颜色
-  }
-
-  //  加载区县边界
-  fetch('/data/韶关市各区县边界图.geojson')
-    .then(res => {
-      if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
-      return res.json();
-    })
-    .then(geojson => {
-      console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
-      
-      L.geoJSON(geojson, {
-        style: (feature) => {
-          const districtName = feature.properties.name || '';
-          const color = getDistrictColor(districtName);
-          return {
-            fillColor: color,
-            fillOpacity: 0.7,
-            color: '#333333',
-            weight: 2,
-          };
-        },
-      }).addTo(map);
-
-      // 加载水系图
-      fetch('/data/韶关市河流水系图.geojson')
-        .then(res => {
-          if (!res.ok) throw new Error(`水系图加载失败:${res.status}`);
-          return res.json();
-        })
-        .then(waterGeojson => {
-          console.log('✅ 水系图数据加载完成,要素数:', waterGeojson.features.length);
-          
-          L.geoJSON(waterGeojson, {
-            style: {
-              color: '#0066CC',
-              weight: 2,
-              opacity: 0.8,
-            },
-          }).addTo(map);
-
-          //  从本地rawData加载采样点数据
-          console.log('✅ 从本地数据加载采样点,数量:', rawData.length);
-          
-          let markerCount = 0;
-          rawData.forEach((item, idx) => {
-            try {
-              console.log(`🔍 处理采样点 #${idx + 1}:`, item);
-              
-              const lng = parseFloat(item.经度);
-              const lat = parseFloat(item.纬度);
-              
-              if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
-                console.warn(`❌ 坐标超出合理范围(第${idx}条):`, lat, lng, item);
-                return;
-              }
-              
-              // 创建蓝色三角形标记
-              const marker = L.marker([lat, lng], {
-                icon: blueTriangle,
-                zIndexOffset: 1000,
-              }).addTo(map);
-
-              // 获取镉含量并格式化
-              const cdValue = parseFloat(item["Cd(ug/L)"]);
-              const formattedCd = isNaN(cdValue) ? '未知' : cdValue.toFixed(2) + ' μg/L';
-              
-              // 绑定弹窗内容
-              marker.bindPopup(`
-                <div class="popup-container">
-                  <h3 class="popup-title">所属河流:</strong> ${item.所属河流}</h3>
-                  <div class="popup-divider"></div>
-    
-                  <p><strong>断面编号:</strong> ${item.断面编号}</p>
-                  <p><strong>断面位置:${item.断面位置}</p>
-                  <p><strong>所属区县:</strong> ${item.所属区县}</p>
-                  <p><strong>镉(Cd)含量:</strong> ${formattedCd} 
-                     
-                </div>
-              `);
-              
-              // 添加鼠标交互效果
-              marker.on('mouseover', () => {
-                marker.getElement().querySelector('svg').style.transform = 'scale(1.2)';
-              }).on('mouseout', () => {
-                marker.getElement().querySelector('svg').style.transform = 'scale(1)';
-              });
-              
-              markerCount++;
-            } catch (err) {
-              console.error(`❌ 处理采样点失败(第${idx}条):`, err);
-            }
-          });
-
-          console.log(`✅ 成功创建 ${markerCount} 个标记点`);
-        })
-        .catch(err => {
-          console.error('❌ 水系图加载失败:', err);
-          alert('水系图加载错误:' + err.message);
-        });
-    })
-    .catch(err => {
-      console.error('❌ 区县边界加载失败:', err);
-      alert('区县边界加载错误:' + err.message);
-    });
-});
-</script>
-
-<style scoped>
-.map-wrapper {
-  width: 100%;
-  height: 100%;
-  position: relative;
-}
-.map-container {
-  width: 100% !important;
-  height: 100% !important;
-}
-
-/* 弹窗样式 */
-::v-deep .popup-title {
-  text-align: center;
-  font-size: 18px;
-  font-weight: 700;
-  color: #0066CC;
-  margin: 0 0 6px;
-  border-bottom: none;
-  padding-bottom: 8px;
-}
-
-::v-deep .popup-divider {
-  height: 1px;
-  background: #0066CC;
-  margin: 8px 0;
-}
-
-::v-deep .popup-container {
-  min-width: 240px;
-  max-width: 300px;
-  padding: 16px;
-  font-family: "Microsoft YaHei", sans-serif;
-}
-
-::v-deep .popup-container p {
-  margin: 6px 0;
-  font-size: 15px;
-  color: #666;
-  line-height: 1.6;
-}
-
-::v-deep .popup-container strong {
-  color: #0066CC;
-  font-weight: 600;
-}
-
-::v-deep .exceeding {
-  color: #FF3333;
-  font-weight: bold;
-}
-
-/* 美化弹窗 */
-::v-deep .leaflet-popup-content-wrapper {
-  padding: 0 !important;
-  border-radius: 12px !important;
-  box-shadow: 0 6px 16px rgba(0,0,0,0.2) !important;
-}
-
-::v-deep .leaflet-popup-content {
-  margin: 0 !important;
-  width: auto !important;
-}
-
-::v-deep .leaflet-popup-tip {
-  display: none;
-}
-
-/* 图例样式 */
-::v-deep .info {
-  padding: 6px 8px;
-  background: white;
-  background: rgba(255,255,255,0.9);
-  box-shadow: 0 0 15px rgba(0,0,0,0.2);
-  border-radius: 5px;
-}
-
-/* 自定义标记样式 */
-::v-deep .custom-div-icon svg {
-  transition: transform 0.2s;
-  display: block;
-}
-</style>

+ 615 - 151
src/views/User/HmOutFlux/irrigationWater/irriWaterInputFlux.vue

@@ -1,206 +1,439 @@
 <template>
   <div class="irrigation-management">
-    <el-card shadow="always" class="gradient-card">
-      <el-row :gutter="20" style="margin-bottom: 10px;">
-        <el-col :span="6">
-          <el-checkbox v-model="waterLand" label="水地" />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="irrigationWaterUsage"
-            placeholder="请输入灌溉水用量"
-            :disabled="!waterLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="irrigationEfficiency"
-            placeholder="请输入灌溉水有效利用率"
-            :disabled="!waterLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-      </el-row>
-
-      <el-row :gutter="20" style="margin-bottom: 10px;">
-        <el-col :span="6">
-          <el-checkbox v-model="irrigatedLand" label="水浇地" />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="irrigatedWaterUsage"
-            placeholder="请输入灌溉水用量"
-            :disabled="!irrigatedLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="irrigatedEfficiency"
-            placeholder="请输入灌溉水有效利用率"
-            :disabled="!irrigatedLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-      </el-row>
-
-      <el-row :gutter="20" style="margin-bottom: 10px;">
-        <el-col :span="6">
-          <el-checkbox v-model="dryLand" label="旱地" />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="dryWaterUsage"
-            placeholder="请输入灌溉水用量"
-            :disabled="!dryLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-        <el-col :span="9">
-          <el-input
-            v-model="dryEfficiency"
-            placeholder="请输入灌溉水有效利用率"
-            :disabled="!dryLand"
-            style="margin-top: 10px;"
-          />
-        </el-col>
-      </el-row>
-
-      <el-row justify="center" style="margin-top: 20px;">
-        <el-button
-          class="calculate-btn"
-          @click="calculateFlux"
-        >
-          计算灌溉水输入通量
-        </el-button>
-      </el-row>
-
-      <div v-if="fluxResult !== null" style="margin-top: 20px; text-align: center; font-weight: bold;">
-        灌溉水输入通量为: {{ fluxResult.toFixed(2) }} m³
-      </div>
-    </el-card>
+    <!-- 计算页面 -->
+    <div v-if="showCalculation">
+      <el-card shadow="always" class="gradient-card">
+        <el-radio-group v-model="selectedLandType" style="width: 100%;">
+          <!-- 水地 -->
+          <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
+            <el-col :span="6">
+              <div class="radio-container">
+                <el-radio label="water" border>水田</el-radio>
+              </div>
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+              <el-input
+                v-model="irrigationWaterUsage"
+                placeholder="请输入灌溉水用量"
+                :disabled="selectedLandType !== 'water'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水有效利用率 (%)</div>
+              <el-input
+                v-model="irrigationEfficiency"
+                placeholder="请输入灌溉水有效利用率"
+                :disabled="selectedLandType !== 'water'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+          </el-row>
+
+          <!-- 水浇地 -->
+          <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
+            <el-col :span="6">
+              <div class="radio-container">
+                <el-radio label="irrigated" border>水浇地</el-radio>
+              </div>
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+              <el-input
+                v-model="irrigatedWaterUsage"
+                placeholder="请输入灌溉水用量"
+                :disabled="selectedLandType !== 'irrigated'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水有效利用率 (%)</div>
+              <el-input
+                v-model="irrigatedEfficiency"
+                placeholder="请输入灌溉水有效利用率"
+                :disabled="selectedLandType !== 'irrigated'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+          </el-row>
+
+          <!-- 旱地 -->
+          <el-row :gutter="20" style="margin-bottom: 10px; align-items: center;">
+            <el-col :span="6">
+              <div class="radio-container">
+                <el-radio label="dry" border>旱地</el-radio>
+              </div>
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水用量 (m³/亩/年)</div>
+              <el-input
+                v-model="dryWaterUsage"
+                placeholder="请输入灌溉水用量"
+                :disabled="selectedLandType !== 'dry'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+            <el-col :span="9">
+              <div class="input-title">灌溉水有效利用率 (%)</div>
+              <el-input
+                v-model="dryEfficiency"
+                placeholder="请输入灌溉水有效利用率"
+                :disabled="selectedLandType !== 'dry'"
+                style="margin-top: 10px;"
+              />
+            </el-col>
+          </el-row>
+        </el-radio-group>
+
+        <el-row justify="center" style="margin-top: 20px;">
+          <el-button
+            class="calculate-btn"
+            @click="calculateFlux"
+            :loading="loading"
+          >
+            计算灌溉水输入通量
+          </el-button>
+        </el-row>
+      </el-card>
+    </div>
+
+    <!-- 结果页面 -->
+    <div v-if="showResults">
+      <el-card shadow="always" class="results-card">
+        <div class="results-header">
+          <el-button 
+          type="primary" 
+          @click="backToCalculation"
+          class="back-button">
+            返回计算
+          </el-button>
+          <div class="result-title">计算结果</div>
+        </div>
+        
+        <div class="results-container">
+          <el-row :gutter="20" style="margin-top: 30px;">
+            <el-col :span="12">
+              <div class="result-subtitle">Cd含量地图</div>
+              <img v-if="mapImageUrl" :src="mapImageUrl" alt="Cd含量地图" class="result-image">
+              <div v-else class="image-placeholder">地图加载中...</div>
+            </el-col>
+            <el-col :span="12">
+              <div class="result-subtitle">Cd含量直方图</div>
+              <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="Cd含量直方图" class="result-image">
+              <div v-else class="image-placeholder">直方图加载中...</div>
+            </el-col>
+          </el-row>
+          
+          <el-row style="margin-top: 30px;">
+            <el-col :span="24">
+              <div class="result-subtitle">统计结果</div>
+              <div class="statistics-container">
+                <!-- 基础统计表格 -->
+                <el-table
+                  v-if="statisticsData"
+                  :data="[statisticsData]"
+                  border
+                  size="small"
+                  style="width: 100%; margin-bottom: 20px;"
+                >
+                  <el-table-column prop="土地类型" label="土地类型" align="center"></el-table-column>
+                  <el-table-column prop="数据更新时间" label="数据更新时间" align="center"></el-table-column>
+                  <el-table-column prop="数据点总数" label="数据点总数" align="center"></el-table-column>
+                  <el-table-column prop="均值" label="均值" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="中位数" label="中位数" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="标准差" label="标准差" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="最小值" label="最小值" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="最大值" label="最大值" align="center" :formatter="formatNumber"></el-table-column>
+                </el-table>
+                
+                <!-- 分位数和形态统计表格 -->
+                <el-table
+                  v-if="statisticsData"
+                  :data="[statisticsData]"
+                  border
+                  size="small"
+                  style="width: 100%; margin-bottom: 20px;"
+                >
+                  <el-table-column prop="25%分位数" label="25%分位数" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="75%分位数" label="75%分位数" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="偏度" label="偏度" align="center" :formatter="formatNumber"></el-table-column>
+                  <el-table-column prop="峰度" label="峰度" align="center" :formatter="formatNumber"></el-table-column>
+                </el-table>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-card>
+    </div>
   </div>
 </template>
 
 <script>
 import { ref } from 'vue';
-import { ElCheckbox, ElInput, ElButton, ElMessage, ElCard, ElRow, ElCol } from 'element-plus';
+import axios from 'axios';
+import { 
+  ElRadio, 
+  ElRadioGroup, 
+  ElInput, 
+  ElButton, 
+  ElMessage, 
+  ElCard, 
+  ElRow, 
+  ElCol, 
+  ElTable, 
+  ElTableColumn 
+} from 'element-plus';
+
+// 土地类型映射
+const landTypeMap = {
+  water: '水田',
+  irrigated: '水浇地',
+  dry: '旱地'
+};
 
 export default {
   components: {
-    ElCheckbox,
+    ElRadio,
+    ElRadioGroup,
     ElInput,
     ElButton,
     ElMessage,
     ElCard,
     ElRow,
-    ElCol
+    ElCol,
+    ElTable,
+    ElTableColumn
   },
   setup() {
-    const waterLand = ref(false);
-    const irrigatedLand = ref(false);
-    const dryLand = ref(false);
-
-    const irrigationWaterUsage = ref('');
-    const irrigationEfficiency = ref('');
+    const selectedLandType = ref('water'); // 默认选择水田
+    
+    // 设置默认值
+    const irrigationWaterUsage = ref('711');
+    const irrigationEfficiency = ref('0.524');
 
-    const irrigatedWaterUsage = ref('');
-    const irrigatedEfficiency = ref('');
+    const irrigatedWaterUsage = ref('427');
+    const irrigatedEfficiency = ref('0.599');
 
-    const dryWaterUsage = ref('');
-    const dryEfficiency = ref('');
+    const dryWaterUsage = ref('200');
+    const dryEfficiency = ref('0.7');
 
     const fluxResult = ref(null);
+    const showCalculation = ref(true); // 显示计算页面
+    const showResults = ref(false); // 显示结果页面
+    const loading = ref(false);
+    
+    // 结果展示数据
+    const mapImageUrl = ref('');
+    const histogramImageUrl = ref('');
+    const statisticsData = ref(null);
+
+    // 格式化数字显示(保留4位小数)
+    const formatNumber = (row, column, cellValue) => {
+      if (typeof cellValue === 'number') {
+        return cellValue.toFixed(4);
+      }
+      return cellValue;
+    };
+
+    // 获取默认地图
+    const fetchDefaultMap = async (landTypeChinese) => {
+      try {
+        const response = await axios.get('http://localhost:8000/api/water/default-map', {
+          params: { land_type: landTypeChinese },
+          responseType: 'blob' // 接收二进制数据
+        });
+        
+        // 创建对象URL
+        return URL.createObjectURL(response.data);
+      } catch (error) {
+        console.error('获取默认地图失败:', error);
+        ElMessage.error('获取地图失败,请重试');
+        return '';
+      }
+    };
+
+    // 获取默认直方图
+    const fetchDefaultHistogram = async (landTypeChinese) => {
+      try {
+        const response = await axios.get('http://localhost:8000/api/water/default-histogram', {
+          params: { land_type: landTypeChinese },
+          responseType: 'blob' // 接收二进制数据
+        });
+        
+        // 创建对象URL
+        return URL.createObjectURL(response.data);
+      } catch (error) {
+        console.error('获取默认直方图失败:', error);
+        ElMessage.error('获取直方图失败,请重试');
+        return '';
+      }
+    };
+
+    // 获取统计数据
+    const fetchStatistics = async (landTypeChinese) => {
+      try {
+        const response = await axios.get('http://localhost:8000/api/water/statistics', {
+          params: { land_type: landTypeChinese }
+        });
+        return response.data;
+      } catch (error) {
+        console.error('获取统计数据失败:', error);
+        ElMessage.error('获取统计数据失败,请重试');
+        return null;
+      }
+    };
+
+    // 返回计算页面
+    const backToCalculation = () => {
+      showCalculation.value = true;
+      showResults.value = false;
+    };
 
-    const calculateFlux = () => {
+    const calculateFlux = async () => {
       let totalFlux = 0;
       let valid = true;
+      let currentLandType = '';
 
-      if (waterLand.value) {
+      if (selectedLandType.value === 'water') {
         if (!irrigationWaterUsage.value || !irrigationEfficiency.value) {
-          ElMessage.warning('请输入水地的灌溉水用量和灌溉水有效利用率');
+          ElMessage.warning('请输入水地的灌溉水用量和有效利用率');
           valid = false;
         } else {
           const usage = parseFloat(irrigationWaterUsage.value);
-          const efficiency = parseFloat(irrigationEfficiency.value);
+          const efficiency = parseFloat(irrigationEfficiency.value) / 100;
 
           if (isNaN(usage) || isNaN(efficiency)) {
             ElMessage.error('请输入有效的数字');
             valid = false;
+          } else if (efficiency > 1 || efficiency < 0) {
+            ElMessage.error('有效利用率应在0-100%之间');
+            valid = false;
           } else {
-            totalFlux += usage * efficiency;
+            totalFlux = usage * efficiency;
+            currentLandType = 'water';
           }
         }
-      }
-
-      if (irrigatedLand.value) {
+      } 
+      else if (selectedLandType.value === 'irrigated') {
         if (!irrigatedWaterUsage.value || !irrigatedEfficiency.value) {
-          ElMessage.warning('请输入水浇地的灌溉水用量和灌溉水有效利用率');
+          ElMessage.warning('请输入水浇地的灌溉水用量和有效利用率');
           valid = false;
         } else {
           const usage = parseFloat(irrigatedWaterUsage.value);
-          const efficiency = parseFloat(irrigatedEfficiency.value);
+          const efficiency = parseFloat(irrigatedEfficiency.value) / 100;
 
           if (isNaN(usage) || isNaN(efficiency)) {
             ElMessage.error('请输入有效的数字');
             valid = false;
+          } else if (efficiency > 1 || efficiency < 0) {
+            ElMessage.error('有效利用率应在0-100%之间');
+            valid = false;
           } else {
-            totalFlux += usage * efficiency;
+            totalFlux = usage * efficiency;
+            currentLandType = 'irrigated';
           }
         }
-      }
-
-      if (dryLand.value) {
+      } 
+      else if (selectedLandType.value === 'dry') {
         if (!dryWaterUsage.value || !dryEfficiency.value) {
-          ElMessage.warning('请输入旱地的灌溉水用量和灌溉水有效利用率');
+          ElMessage.warning('请输入旱地的灌溉水用量和有效利用率');
           valid = false;
         } else {
           const usage = parseFloat(dryWaterUsage.value);
-          const efficiency = parseFloat(dryEfficiency.value);
+          const efficiency = parseFloat(dryEfficiency.value) / 100;
 
           if (isNaN(usage) || isNaN(efficiency)) {
             ElMessage.error('请输入有效的数字');
             valid = false;
+          } else if (efficiency > 1 || efficiency < 0) {
+            ElMessage.error('有效利用率应在0-100%之间');
+            valid = false;
           } else {
-            totalFlux += usage * efficiency;
+            totalFlux = usage * efficiency;
+            currentLandType = 'dry';
           }
         }
+      } 
+      else {
+        ElMessage.warning('请选择一种土地类型');
+        valid = false;
       }
 
       if (valid) {
+        loading.value = true;
         fluxResult.value = totalFlux;
-        ElMessage.success(`灌溉水输入通量为: ${totalFlux.toFixed(2)} m³`);
+        
+        try {
+          // 获取土地类型中文名称
+          const landTypeChinese = landTypeMap[currentLandType];
+          
+          // 第一步:调用calculate接口进行计算
+          const formData = new FormData();
+          formData.append('land_type', landTypeChinese);
+          formData.append('param1', totalFlux);
+          formData.append('param2', parseFloat(irrigationEfficiency.value || irrigatedEfficiency.value || dryEfficiency.value));
+          formData.append('color_map_name', "绿-黄-红-紫");
+          formData.append('output_size', 8);
+
+          await axios.post('http://localhost:8000/api/water/calculate', formData, {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            }
+          });
+          
+          // 第二步:获取默认地图
+          mapImageUrl.value = await fetchDefaultMap(landTypeChinese);
+          
+          // 第三步:获取默认直方图
+          histogramImageUrl.value = await fetchDefaultHistogram(landTypeChinese);
+          
+          // 第四步:获取统计数据
+          statisticsData.value = await fetchStatistics(landTypeChinese);
+          
+          // 切换到结果页面
+          showCalculation.value = false;
+          showResults.value = true;
+          
+          ElMessage.success('计算完成,结果已展示');
+        } catch (error) {
+          console.error('获取结果失败:', error);
+          ElMessage.error('获取结果失败,请重试');
+        } finally {
+          loading.value = false;
+        }
       }
     };
 
     return {
-      waterLand,
-      irrigatedLand,
-      dryLand,
-
+      selectedLandType,
       irrigationWaterUsage,
       irrigationEfficiency,
-
       irrigatedWaterUsage,
       irrigatedEfficiency,
-
       dryWaterUsage,
       dryEfficiency,
-
       calculateFlux,
-      fluxResult
+      fluxResult,
+      showCalculation,
+      showResults,
+      loading,
+      mapImageUrl,
+      histogramImageUrl,
+      statisticsData,
+      formatNumber,
+      backToCalculation
     };
   }
 };
 </script>
 
 <style scoped>
-.irrigation-management {
-  padding: 20px;
+.compact-container {
   display: flex;
   justify-content: center;
   align-items: center;
+  flex-direction: column;
 }
 
 .gradient-card {
@@ -211,7 +444,7 @@ export default {
     rgba(137, 223, 252, 0.8)
   );
   width: 80%;
-  max-width: 600px;
+  max-width: 800px;
   padding: 25px;
   box-sizing: border-box;
   border-radius: 12px;
@@ -220,56 +453,287 @@ export default {
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-.el-checkbox {
-  margin-bottom: 10px;
+.results-card {
+ background: linear-gradient(
+    135deg, 
+    rgba(250, 253, 255, 0.8), 
+    rgba(137, 223, 252, 0.8)
+  );
+  width: 90%;
+  max-width: 1200px;
+  padding: 30px;
+  box-sizing: border-box;
+  border-radius: 12px;
+  border: none;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
+}
+
+.results-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.result-title {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+  text-align: center;
+  flex-grow: 1;
+}
+
+/* 输入框标题样式 */
+.input-title {
+  font-size: 14px;
   font-weight: 500;
+  color: #606266;
+  margin-bottom: 5px;
+  text-align: left;
 }
 
-.el-input {
-  width: 100%;
+/* 单选框容器 */
+.radio-container {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  height: 100%;
 }
 
-/* 使用 Vue 3 推荐的 :deep() 选择器 */
-:deep(.el-input) .el-input__inner {
-  border-radius: 6px;
+.compact-title-section {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.compact-main-title {
+  font-size: 1.5rem;
+  color: #1a3c7a;
+  margin-bottom: 8px;
+  font-weight: 600;
+}
+
+.compact-sub-title {
+  font-size: 0.9rem;
+  color: #555;
+  margin-bottom: 10px;
+}
+
+.compact-input-section {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+}
+
+.compact-land-section {
+  display: flex;
+  align-items: center;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 10px;
+  padding: 12px 15px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  transition: all 0.2s ease;
+}
+
+.compact-land-section:hover {
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+  transform: translateY(-2px);
+}
+
+.compact-land-checkbox {
+  width: 80px;
+}
+
+.compact-land-label {
+  font-weight: 600;
+  font-size: 1rem;
+  color: #1a3c7a;
+}
+
+.compact-input-group {
+  flex: 1;
+  margin: 0 8px;
+}
+
+.compact-input-label {
+  font-size: 0.85rem;
+  font-weight: 500;
+  margin-bottom: 6px;
+  color: #2c3e50;
+}
+
+:deep(.compact-input .el-input__inner) {
+  height: 36px;
+  font-size: 0.9rem;
+  border-radius: 8px;
+  padding: 0 12px;
   border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  background: rgba(255, 255, 255, 0.03); /* 输入框半透明白色背景 */
 }
 
-:deep(.el-input) .el-input__inner:focus {
+:deep(.compact-input .el-input__inner:focus) {
   border-color: #409EFF;
   box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
 }
 
+:deep(.el-radio) {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  text-align: left;
+}
+
+:deep(.el-radio__label) {
+  text-align: left;
+}
+
 .calculate-btn {
   width: 100%;
   max-width: 300px;
-  height: 50px;
+  height: 42px;
   border: none;
-  border-radius: 25px !important;
+  border-radius: 8px;
+  font-size: 1rem;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  background: linear-gradient(45deg, #1a8cff, #00cc99);
+  color: white;
+  box-shadow: 0 4px 10px rgba(26, 140, 255, 0.3);
+}
+
+.compact-calculate-btn:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 12px rgba(26, 140, 255, 0.4);
+  background: linear-gradient(45deg, #0d7de0, #00b386);
+}
+
+.compact-calculate-btn:active {
+  transform: translateY(1px);
+}
+
+.compact-result-section {
+  text-align: center;
+  margin-top: 15px;
+  padding: 15px;
+  border-radius: 10px;
+  background: rgba(255, 255, 255, 0.95);
+  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
+  border: 1px solid #e0f7fa;
+}
+
+.compact-result-title {
+  font-size: 1.1rem;
+  color: #1a3c7a;
+  margin-bottom: 10px;
+  font-weight: 600;
+}
+
+.compact-flux-value {
+  font-size: 1.8rem;
+  font-weight: 700;
+  color: #e91e63;
+  margin: 5px 0;
+}
+
+.compact-unit {
+  font-size: 1.2rem;
+  color: #555;
+  font-weight: 600;
+}
+
+.compact-result-description {
+  font-size: 0.9rem;
+  color: #555;
+  margin-top: 5px;
+}
+
+/* 结果区域样式 */
+.results-container {
+  margin-top: 20px;
+}
+
+.result-subtitle {
+  text-align: center;
+  font-weight: bold;
+  font-size: 20px;
+  margin-bottom: 15px;
+  color: #333;
+  padding-bottom: 5px;
+  border-bottom: 1px solid #eee;
+}
+
+.result-image {
+  width: 100%;
+  max-height: 400px;
+  object-fit: contain;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  background-color: #f8f8f8;
+}
+
+.image-placeholder {
+  height: 400px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1px dashed #dcdfe6;
+  border-radius: 8px;
+  background-color: #f5f7fa;
+  color: #909399;
+  font-style: italic;
   font-size: 18px;
+}
+
+.statistics-container {
+  background-color: rgba(255, 255, 255, 0.7);
+  border-radius: 12px;
+  padding: 25px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+  margin-top: 20px;
+}
+
+/* 表格样式增强 */
+:deep(.el-table) {
+  margin-bottom: 25px;
+  border-radius: 10px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+:deep(.el-table__header) {
+  background-color: #f0f8ff;
+}
+
+:deep(.el-table th) {
+  background-color: #f0f8ff;
   font-weight: bold;
-  transition: all 0.4s ease;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+  color: #2c3e50;
+  font-size: 16px;
 }
 
-.calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
-  background: linear-gradient(to right, #7de8df, #20a03d);
+:deep(.el-table td) {
+  font-size: 15px;
 }
 
-.calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+/* 返回按钮样式 */
+.back-button {
+  position: absolute;
+  top: 20px;
+  left: 20px; /* 从right改为left */
+  width: 120px;
+  font-size: 16px;
+  padding: 10px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
+  border: none;
+  border-radius: 20px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  z-index: 10; /* 确保按钮在顶层 */
+}
+
+.back-button:hover {
+  background: linear-gradient(to right, #7de8df, #20a03d);
 }
 </style>

+ 128 - 17
src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue

@@ -1,6 +1,11 @@
 <template>
   <div class="page-container">
-    
+    <div class="header">
+        <h1>灌溉水采样点分析系统</h1>
+        <p>韶关市水环境重金属监测数据可视化分析平台</p>
+      </div>
+
+  <div class="main-content">
    <div class="point-map">
     <div class="component-title">采样点地图展示</div>
     <div class="map-holder">
@@ -19,12 +24,13 @@
     <Waterassaydata2/>
    </div>
   </div>
+  </div>
 </template>
 
 <script setup>
-import Waterdataline from './waterdataline.vue';
-import Waterassaydata2 from './waterassaydata2.vue';
-import irrwatermap from './irrwatermap.vue';
+import Waterdataline from '@/components/irrpollution/waterdataline.vue';
+import Waterassaydata2 from '@/components/irrpollution/waterassaydata2.vue';
+import irrwatermap from '@/components/irrpollution/irrwatermap.vue';
 
 
 
@@ -34,14 +40,14 @@ import irrwatermap from './irrwatermap.vue';
 <style scoped>
 .map-holder {
   position: relative;
-  height: calc(100% - 40px); /* 减去标题高度,避免覆盖 */
+  height: 60%; 
   z-index: 100;
 }
 .page-container {
   display: flex;
   flex-direction: column;
-  height: 100vh; /* 整屏高度 */
-  padding: 0;
+  min-height: 100vh;
+  padding: 20px;
   box-sizing: border-box;
   background-color: #f5f7fa;
   gap: 20px;
@@ -76,9 +82,60 @@ import irrwatermap from './irrwatermap.vue';
   padding: 16px;
   box-sizing: border-box;
   max-width: 1800px;
+  margin: 0 auto;
   width: 100%;
-  margin: 20px auto;
 }
+
+.header {
+  text-align: center;
+  padding: 20px 0 30px;
+  margin-bottom: 20px;
+}
+
+.header h1 {
+  color: #1e88e5;
+  font-size: 2.5rem;
+  margin-bottom: 10px;
+  text-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.header p {
+  color: #546e7a;
+  font-size: 1.2rem;
+  max-width: 600px;
+  margin: 0 auto;
+}
+
+.content-wrapper {
+  display: flex;
+  flex-direction: column;
+  gap: 25px;
+  flex: 1;
+}
+
+.single-row {
+  background-color: rgba(255, 255, 255, 0.85);
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  transition: transform 0.3s ease;
+  width: 100%;
+}
+
+.single-row:hover {
+  transform: translateY(-5px);
+}
+
+.map-container {
+  height: 500px;
+  padding: 10px;
+}
+
+.data-container, .chart-container {
+  padding: 10px;
+  height: 350px;
+}
+
 .component-title {
   /* 基础布局:左对齐 + 紧凑间距 */
   text-align: left;        /* 强制左对齐,告别居中 */
@@ -87,23 +144,77 @@ import irrwatermap from './irrwatermap.vue';
   position: relative;      /* 为伪元素定位做准备 */
   z-index: 10000;
   /* 文字样式:简约但醒目 */
-  font-size: 1.7rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
+  font-size: 1.5rem;      /* 稍小一号,更克制(原1.5rem→1.25rem) */
   font-weight: 600;        /* 适度加粗,比正文突出但不夸张 */
   color: #1e88e5;          /* 统一蓝色,和方块颜色呼应 */
   line-height: 1.2;        /* 紧凑行高,避免臃肿 */
 }
 
-/* 蓝色小方块:用伪元素实现,无额外HTML */
 .component-title::before {
   content: "";
   position: absolute;
-  left: 0;                /* 靠最左侧 */
-  top: 50%;              /* 垂直居中 */
+  left: 8px;
+  top: 50%;
   transform: translateY(-50%);
-  width: 12px;           /* 方块大小,适中即可 */
-  height: 12px;
-  background-color: #1e88e5; /* 和文字同色,统一感 */
-  border-radius: 2px;    /* 轻微圆角,比直角更柔和 */
+  width: 8px;
+  height: 8px;
+  background-color: #1e88e5;
+  border-radius: 50%;
 }
 
-</style>
+.footer {
+  text-align: center;
+  padding: 20px;
+  margin-top: 30px;
+  color: #546e7a;
+  font-size: 0.9rem;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .map-container {
+    height: 450px;
+  }
+}
+
+@media (max-width: 768px) {
+  .header h1 {
+    font-size: 2rem;
+  }
+  
+  .header p {
+    font-size: 1rem;
+  }
+  
+  .map-container {
+    height: 400px;
+  }
+  
+  .data-container, .chart-container {
+    height: 300px;
+  }
+  
+  .component-title {
+    font-size: 1.3rem;
+    padding: 12px 12px 12px 20px;
+  }
+}
+
+@media (max-width: 480px) {
+  .header h1 {
+    font-size: 1.8rem;
+  }
+  
+  .component-title {
+    font-size: 1.2rem;
+  }
+  
+  .map-container {
+    height: 350px;
+  }
+  
+  .page-container {
+    padding: 15px;
+  }
+}
+</style>

+ 0 - 310
src/views/User/HmOutFlux/irrigationWater/irrwatermap.vue

@@ -1,310 +0,0 @@
-<template>
-  <div class="map-wrapper" @click.stop>
-    <div ref="mapContainer" class="map-container"></div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue';
-import L from 'leaflet';
-import 'leaflet/dist/leaflet.css';
-
-const mapContainer = ref(null);
-
-
-
-onMounted(() => {
-  // 初始化地图(强制确保容器可用)
-  if (!mapContainer.value) {
-    console.error('❌ 地图容器未找到!');
-    return;
-  }
-
-  // 定义位置格式化函数(处理"广东省韶关市"前缀)
-  const formatLocation = (fullLocation) => {
-    if (!fullLocation) return '未知位置'; // 处理空值
-    // 移除前缀并清理空格
-    const processed = fullLocation.replace(/^广东省韶关市/, '').trim();
-    // 处理移除后为空的情况
-    return processed || '未知位置';
-  };
-
-  const map = L.map(mapContainer.value, {
-    center: [25, 114], // 韶关大致中心  前大往下,后大往左
-    zoom: 8.5,
-    minZoom:8.3,
-  });
-
-  // 区县颜色映射(与GeoJSON的properties.name严格匹配)
-  const districtColorMap = {
-    "武江区": "#FF6B6B",
-    "浈江区": "#4ECDC4",
-    "曲江区": "#FFD166",
-    "始兴县": "#A0DAA9",
-    "仁化县": "#6A0572",
-    "翁源县": "#1A535C",
-    "乳源瑶族自治县": "#FF9F1C",
-    "新丰县": "#87CEEB",
-    "乐昌市": "#118AB2",
-    "南雄市": "#06D6A0",
-  };
-
-  // 加载区县边界(带完整错误处理)
-  fetch('/data/韶关市各区县边界图.geojson')
-    .then(res => {
-      if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
-      return res.json();
-    })
-    .then(geojson => {
-      console.log('✅ 区县边界数据加载完成,要素数:', geojson.features.length);
-      
-      L.geoJSON(geojson, {
-        style: (feature) => {
-          const districtName = feature.properties.name; // 匹配GeoJSON的name字段
-          const color = districtColorMap[districtName] || '#cccccc';
-          return {
-            fillColor: color,
-            fillOpacity: 0.7,
-            color: '#333333', // 边界颜色
-            weight: 2,        // 边界宽度
-          };
-        },
-      }).addTo(map);
-
-      // 加载水系图(新增,带样式和错误处理)
-      fetch('/data/韶关市河流水系图.geojson')
-        .then(res => {
-          if (!res.ok) throw new Error(`水系图加载失败:${res.status}`);
-          return res.json();
-        })
-        .then(waterGeojson => {
-          console.log('✅ 水系图数据加载完成,要素数:', waterGeojson.features.length);
-          
-          L.geoJSON(waterGeojson, {
-            style: {
-              color: '#0066CC', // 水系颜色
-              weight: 2,       // 线条宽度
-              opacity: 0.8,    // 透明度
-            },
-          }).addTo(map);
-
-          // 加载采样点和检测数据(并行请求)
-          Promise.all([
-            fetch('http://localhost:3000/table/Water_sampling_data').then(res => res.json()),
-            fetch('http://localhost:3000/table/Water_assay_data').then(res => res.json())
-          ])
-          .then(([samplingData, assayData]) => {
-            console.log('✅ 采样点数据:', samplingData.length, '条;检测数据:', assayData.length, '条');
-            
-            // 构建检测数据映射
-            const assayMap = {};
-            assayData.forEach(item => {
-              assayMap[item.water_sample_ID] = item;
-            });
-
-            let markerCount = 0;
-            samplingData.forEach((sample, idx) => {
-              try {
-                // 打印原始数据(用于后续分析)
-               // console.log(`🔍 处理采样点 #${idx + 1}:`, sample);
-                
-                // 智能提取经纬度字段(支持多种可能的字段名)
-                const latField = ['latitude', 'lat', 'Latitude', 'Lat'].find(key => sample[key] !== undefined);
-                const lngField = ['longitude', 'lng', 'Longitude', 'Lng'].find(key => sample[key] !== undefined);
-                
-                if (!latField || !lngField) {
-                  console.error(`❌ 未找到经纬度字段(第${idx}条):`, sample);
-                  return;
-                }
-                
-                //  清理并转换经纬度(处理特殊字符和逗号)
-                const cleanLat = String(sample[latField]).replace(/[^\d.-]/g, '');
-                const cleanLng = String(sample[lngField]).replace(/[^\d.-]/g, '');
-                
-                // 强制四舍五入到6位小数(避免精度问题)
-                const lat = parseFloat(parseFloat(cleanLat).toFixed(6));
-                const lng = parseFloat(parseFloat(cleanLng).toFixed(6));
-                
-                // 范围校验(扩大范围10%,兼容边界值)
-                if (isNaN(lat) || isNaN(lng) || lat < 22.7 || lat > 25.5 || lng < 112.7 || lng > 115.3) {
-                  console.warn(`❌ 坐标超出合理范围(第${idx}条):`, lat, lng, sample);
-                  return;
-                }
-                
-                // 增强型检测数据匹配(忽略大小写 + 部分匹配)
-                const sampleId = String(sample.water_sample_ID || '').trim().toLowerCase();
-                let data = assayMap[sampleId];
-                
-                // 若未找到,尝试模糊匹配
-                if (!data && sampleId) {
-                  data = Object.values(assayMap).find(
-                    item => item.water_sample_ID?.toLowerCase().includes(sampleId)
-                  );
-                  
-                  if (data) {
-                    console.log(`⚠️ 部分匹配检测数据:${sampleId} → ${data.water_sample_ID}`);
-                  }
-                }
-                
-                if (!data) {
-                  console.warn(`❌ 无匹配检测数据(ID: ${sampleId},第${idx}条)`);
-                  return;
-                }
-                
-                // 创建标记点(使用 L.circleMarker 而非 L.marker)
-                const marker = L.circleMarker([lat, lng], {
-                  radius: 4,                  // 增大圆点半径,确保可见
-                  color: '#FF3333',           // 边框颜色(红色)
-                  fillColor: '#FF3333',       // 填充颜色(红色)
-                  fillOpacity: 0.9,           // 填充透明度(接近不透明)
-                  weight: 2,                  // 边框宽度(加粗)
-                  zIndexOffset: 1000,         // 提高层级,确保在所有图层之上
-                }).addTo(map);
-
-                
-                
-               marker.bindPopup(`
-                 <div class="popup-container">
-                   <!-- 1. 顶部标题(假设地区信息来自 sample.location,若无则固定) -->
-                   <h3 class="popup-title">${formatLocation(sample.sampling_location) }</h3>
-                   <div class="popup-divider"></div> <!-- 分隔线 -->
-    
-                   <!-- 3. 检测数据表格 -->
-                   <table class="popup-table">
-                     <thead>
-                       <tr>
-                         <th>检测项</th>
-                         <th>数值</th>
-                       </tr>
-                     </thead>
-                     <tbody>
-                       <tr><td>Ph</td><td>${data.pH || '未知'}</td></tr>
-                       <tr><td>铬(Cr)</td><td>${data.Cr || '未知'}</td></tr>
-                       <tr><td>砷(As)</td><td>${data.As || '未知'}</td></tr>
-                       <tr><td>镉(Cd)</td><td>${data.Cd || '未知'}</td></tr>
-                       <tr><td>汞(Hg)</td><td>${data.Hg || '未知'}</td></tr>
-                       <tr><td>铅(Pb)</td><td>${data.Pb || '未知'}</td></tr>
-                     </tbody>
-                   </table>
-                 </div>
-               `);
-                
-                markerCount++;
-              } catch (err) {
-                console.error(`❌ 处理采样点失败(第${idx}条):`, err);
-              }
-            });
-
-            console.log(`✅ 成功创建 ${markerCount} 个标记点`);
-          })
-          .catch(err => {
-            console.error('❌ 采样/检测数据加载失败:', err);
-            alert('数据接口错误:' + err.message);
-          });
-        })
-        .catch(err => {
-          console.error('❌ 水系图加载失败:', err);
-          alert('水系图加载错误:' + err.message);
-        });
-    })
-    .catch(err => {
-      console.error('❌ 区县边界加载失败:', err);
-      alert('区县边界加载错误:' + err.message);
-    });
-});
-</script>
-
-<style scoped>
-.map-wrapper {
-  width:100%;
-  height: 100%;
-  position: relative;
-  z-index: 100;
-}
-.map-container {
-  width: 100% !important;
-  height: 100% !important;
-}
-
-/*  标题和分隔线 */
-::v-deep .popup-title {
-  text-align: center;       /* 居中 */
-  font-size: 16px;          /* 减小字号 */
-  font-weight: 700;         /* 加粗 */
-  color: #0066CC;           /* 蓝色,匹配设计 */
-  margin: 0 0 4px;          /* 间距调整 */
-  border-bottom: 2px solid #0066CC; /* 底部横线 */
-  padding-bottom: 4px;      /* 横线与文字间距 */
-}
-
-::v-deep .popup-divider {
-  height: 1px;              /* 横线高度 */
-  background: #0066CC;      /* 横线颜色 */
-  margin: 6px 0;            /* 上下间距 */
-}
-
-/*  表格样式 */
-::v-deep .popup-table {
-  width: 100%;              /* 占满容器 */
-  border-collapse: collapse;/* 合并边框 */
-  margin-top: 12px;         /* 与段落间距 */
-}
-
-::v-deep .popup-table th,
-::v-deep .popup-table td {
-  border: 1px solid #CCCCCC;/* 单元格边框 */
-  padding: 4px 6px;         /* 内边距 */
-  text-align: center;       /* 内容居中 */
-  font-size: 12px;          /* 字号调整 */
-}
-
-::v-deep .popup-table th {
-  background: #F5F5F5;      /* 表头背景色 */
-  font-weight: 600;         /* 表头加粗 */
-}
-
-/* 美化弹窗(完整层级穿透) */
-::v-deep .leaflet-popup-content-wrapper {
-  padding: 0 !important;
-  border-radius: 12px !important;
-  box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
-}
-
-::v-deep .leaflet-popup-content {
-  margin: 0 !important;
-  width: auto !important;
-  max-width: 220px !important;
-}
-
-::v-deep .popup-container {
-  min-width: 180px;
-  max-width: 220px;
-  padding: 10px;/**内边距 */
-  font-family: "Microsoft YaHei", sans-serif;
-}
-
-::v-deep .popup-content p {
-  margin: 6px 0;
-  font-size: 15px;
-  color: #666;
-  line-height: 1.6;
-}
-
-::v-deep .popup-content strong {
-  color: #FF3333; /* 与标记点颜色呼应 */
-  font-weight: 600;
-}
-
-/* 可选:隐藏弹窗箭头,更像卡片 */
-::v-deep .leaflet-popup-tip {
-  display: none;
-}
-
-/* 临时调试:确保标记点可见 */
-::v-deep .leaflet-marker-icon {
-  display: none !important; /* 隐藏默认标记图标 */
-}
-::v-deep .leaflet-circle-marker {
-  stroke-width: 2px !important;
-}
-</style>

+ 125 - 40
src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue

@@ -1,24 +1,46 @@
 <template>
   <div class="sampling-process">
-    <el-card shadow="always" style="margin-bottom: 20px;">
-      <h2>1.采样容器与过程</h2>
-      <p>采样容器均为500mL的白色聚乙烯瓶,采样体积均为500mL,采样过程在不同天气条件下进行,主要天气状况包括多云、阴天和小雨,采样点周边环境主要为河流,只有少数样品采集于水渠或瀑布区域。</p>
-      <div class="image-row">
-        <el-image :src="image1" alt="图1" style="width: 30%; margin-right: 2%"></el-image>
-        <el-image :src="image2" alt="图2" style="width: 30%; margin-right: 2%"></el-image>
-        <el-image :src="image3" alt="图3" style="width: 30%;"></el-image>
+    <h2>1.采样容器与过程</h2>
+    <p>
+      采样容器均为500mL的白色聚乙烯瓶,采样体积均为500mL,采样过程在不同天气条件下进行,主要天气状况包括多云、阴天和小雨,
+      采样点周边环境主要为河流,只有少数样品采集于水渠或瀑布区域。
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="image1" alt="采样容器" class="sampling-image"></el-image>
+        <p class="image-caption">图1-1 采样容器</p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image2" alt="采样现场" class="sampling-image"></el-image>
+        <p class="image-caption">图1-2 采样现场</p>
+      </div>
+      <div class="image-container">
+        <el-image :src="image3" alt="灌溉水采样设备" class="sampling-image"></el-image>
+        <p class="image-caption">图1-3 灌溉水采样设备</p>
       </div>
-      <p class="caption">图 3 灌溉水采样设备</p>
-
-      <h2>2.样品保存与现场情况</h2>
-      <p>绝大多数样品状态为无色、无沉淀、无味、无悬浮物,只有少量样品稍显浑浊并含有沉淀物,为了保证样品的完整性和数据的准确性,采样后的保存方式包括了冷藏、避光、确保标签完好以及采取有效的减震措施,以避免运输过程中的振动和损坏。</p>
-      <div class="image-row">
-        <el-image :src="fieldImage1" alt="图4-1" style="width: 30%; margin-right: 2%"></el-image>
-        <el-image :src="fieldImage2" alt="图4-2" style="width: 30%; margin-right: 2%"></el-image>
-        <el-image :src="fieldImage3" alt="图4-3" style="width: 30%;"></el-image>
+    </div>
+
+    <h2>2.样品保存与现场情况</h2>
+    <p>
+      绝大多数样品状态为无色、无沉淀、无味、无悬浮物,只有少量样品稍显浑浊并含有沉淀物,为了保证样品的完整性和数据的准确性,
+      采样后的保存方式包括了冷藏、避光、确保标签完好以及采取有效的减震措施,以避免运输过程中的振动和损坏。
+    </p>
+
+    <div class="image-row">
+      <div class="image-container">
+        <el-image :src="fieldImage1" alt="工作人员采样现场" class="sampling-image"></el-image>
+        <p class="image-caption">图2-1 采样现场</p>
       </div>
-      <p class="caption">图 4 工作人员采样现场</p>
-    </el-card>
+      <div class="image-container">
+        <el-image :src="fieldImage2" alt="工作人员采样现场" class="sampling-image"></el-image>
+        <p class="image-caption">图2-2 采样现场</p>
+      </div>
+      <div class="image-container">
+        <el-image :src="fieldImage3" alt="工作人员采样现场" class="sampling-image"></el-image>
+        <p class="image-caption">图2-3 采样现场</p>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -26,7 +48,7 @@
 export default {
   data() {
     return {
-      image1: '/图1.png', 
+      image1: '/图1.png',
       image2: '/图片2.png',
       image3: '/图片3.png',
       fieldImage1: '/图片4.jpg',
@@ -40,36 +62,99 @@ export default {
 <style scoped>
 .sampling-process {
   padding: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  min-height: 100vh;
+  position: relative;
+  overflow: hidden;
 }
-.el-card {
-  background-color: rgba(255, 255, 255, 0.3); 
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  border-radius: 8px;
+
+.sampling-process::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: url('https://images.unsplash.com/photo-1518834107812-67b0b7c58434?q=80&w=2070&auto=format&fit=crop') center/cover;
+  opacity: 0.3;
+  z-index: -1;
 }
-.el-card h2 {
-  font-size: 24px;
-  margin-bottom: 10px;
+
+.image-row {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin: 30px 0;
+  gap: 20px;
 }
 
-P {
+p {
   text-indent: 2em;
 }
-.el-card p {
-  line-height: 1.6;
+
+.image-container {
+  flex: 1;
+  min-width: 280px;
+  border-radius: 12px;
+  overflow: hidden;
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  background: rgba(255, 255, 255, 0.7);
+  border: 1px solid rgba(255, 255, 255, 0.5);
 }
-.image-row {
-  display: flex;
-  justify-content: space-between;
-  margin-top: 10px;
+
+.image-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  background: rgba(255, 255, 255, 0.85);
 }
-.el-image {
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
+
+.sampling-image {
+  border-radius: 12px 12px 0 0 !important;
+  overflow: hidden;
+  border: none !important;
+  width: 100% !important;
+  height: 250px;
+  display: block;
+  transition: transform 0.5s ease;
+  background: rgba(255, 255, 255, 0.4);
+}
+
+.image-container:hover .sampling-image {
+  transform: scale(1.03);
 }
-.caption {
+
+.image-caption {
   text-align: center;
-  font-size: 14px;
-  color: #000; /* 确保图注为黑色 */
-  margin-top: 10px;
+  font-size: 15px;
+  color: #2d3748;
+  padding: 12px;
+  font-weight: 500;
+  background: rgba(248, 250, 252, 0.7);
+  margin: 0;
+}
+
+/* 响应式设计 */
+@media (max-width: 900px) {
+  .image-container {
+    min-width: 48%;
+  }
+}
+
+@media (max-width: 768px) {
+
+  .image-container {
+    min-width: 100%;
+  }
+}
+
+@media (max-width: 480px) {
+  .sampling-process {
+    padding: 10px;
+  }
+
+  .sampling-image {
+    height: 200px;
+  }
 }
-</style>
+</style>

+ 14 - 57
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -539,16 +539,14 @@ export default {
 </script>
 
 <style scoped>
-::v-deep .el-table th.el-table__cell {
-  text-align: center;
-  background-color: #f5f7fa !important;
-}
-::v-deep .el-table td.el-table__cell {
-  text-align: center;
-}
 .container {
   padding: 20px;
-  background-color: #f5f7fa;
+  /* 添加70%透明度的渐变背景 */
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
   min-height: 100vh;
   box-sizing: border-box;
 }
@@ -559,9 +557,10 @@ export default {
   gap: 15px;
   margin-bottom: 20px;
   padding: 15px;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .upload-section {
@@ -569,7 +568,7 @@ export default {
   align-items: center;
   gap: 15px;
   padding-bottom: 15px;
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
 }
 
 .file-name {
@@ -619,12 +618,13 @@ export default {
 .map-section, .histogram-section {
   flex: 1;
   min-width: 300px;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .map-image, .histogram-image {
@@ -635,32 +635,14 @@ export default {
   border-radius: 4px;
 }
 
-.stats-area {
+.table-area {
   width: 100%;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-}
-
-.stats-container {
   margin-top: 20px;
-}
-
-.charts-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  margin-top: 30px;
-}
-
-.chart-item {
-  flex: 1;
-  min-width: 300px;
-  background: #f9f9f9;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .loading-container {
@@ -713,29 +695,4 @@ export default {
     flex: none;
   }
 }
-.model-info {
-  display: flex;
-  align-items: center;
-  gap: 15px;
-  margin: 10px 0;
-  color: #666;
-}
-
-.update-time {
-  font-size: 14px;
-}
-
-.chart-item {
-  flex: 1;
-  min-width: 400px;
-  background: white;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
-  margin-bottom: 20px;
-}
-
-.el-table {
-  margin-top: 20px;
-}
 </style>

+ 14 - 57
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -539,16 +539,14 @@ export default {
 </script>
 
 <style scoped>
-::v-deep .el-table th.el-table__cell {
-  text-align: center;
-  background-color: #f5f7fa !important;
-}
-::v-deep .el-table td.el-table__cell {
-  text-align: center;
-}
 .container {
   padding: 20px;
-  background-color: #f5f7fa;
+  /* 添加70%透明度的渐变背景 */
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
   min-height: 100vh;
   box-sizing: border-box;
 }
@@ -559,9 +557,10 @@ export default {
   gap: 15px;
   margin-bottom: 20px;
   padding: 15px;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .upload-section {
@@ -569,7 +568,7 @@ export default {
   align-items: center;
   gap: 15px;
   padding-bottom: 15px;
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
 }
 
 .file-name {
@@ -619,12 +618,13 @@ export default {
 .map-section, .histogram-section {
   flex: 1;
   min-width: 300px;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .map-image, .histogram-image {
@@ -635,32 +635,14 @@ export default {
   border-radius: 4px;
 }
 
-.stats-area {
+.table-area {
   width: 100%;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-}
-
-.stats-container {
   margin-top: 20px;
-}
-
-.charts-container {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20px;
-  margin-top: 30px;
-}
-
-.chart-item {
-  flex: 1;
-  min-width: 300px;
-  background: #f9f9f9;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .loading-container {
@@ -713,29 +695,4 @@ export default {
     flex: none;
   }
 }
-.model-info {
-  display: flex;
-  align-items: center;
-  gap: 15px;
-  margin: 10px 0;
-  color: #666;
-}
-
-.update-time {
-  font-size: 14px;
-}
-
-.chart-item {
-  flex: 1;
-  min-width: 400px;
-  background: white;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
-  margin-bottom: 20px;
-}
-
-.el-table {
-  margin-top: 20px;
-}
 </style>

+ 14 - 6
src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue

@@ -297,7 +297,12 @@ export default {
 <style scoped>
 .container {
   padding: 20px;
-  background-color: #f5f7fa;
+  /* 添加70%透明度的渐变背景 */
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
   min-height: 100vh;
   box-sizing: border-box;
 }
@@ -308,9 +313,10 @@ export default {
   gap: 15px;
   margin-bottom: 20px;
   padding: 15px;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .upload-section {
@@ -318,7 +324,7 @@ export default {
   align-items: center;
   gap: 15px;
   padding-bottom: 15px;
-  border-bottom: 1px solid #eee;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
 }
 
 .file-name {
@@ -367,13 +373,14 @@ export default {
 
 .map-section, .histogram-section {
   flex: 1;
-  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
-  background-color: white;
+  min-width: 300px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .map-image, .histogram-image {
@@ -386,11 +393,12 @@ export default {
 
 .table-area {
   width: 100%;
-  background-color: white;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   margin-top: 20px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
 .loading-container {

+ 269 - 166
src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue

@@ -1,238 +1,341 @@
 <template>
-  <div class="crop-cd-model-container">
-    <el-card shadow="always" class="gradient-card">
-      <h2>作物镉模型计算</h2>
-      
-      <el-button class="enable-btn" @click="enableInputs">作物镉模型计算</el-button>
-      
-      <el-form label-width="250px" label-position="top" class="form-container">
-        <div class="form-section">
-          <!-- 作物亩产量 (单独一行) -->
-          <div class="input-row single-input">
-            <el-form-item label="作物亩产量 (斤)" class="input-item">
-              <el-input 
-                v-model="yieldPerMu" 
-                placeholder="800" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-          </div>
-          
-          <div class="input-row">
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="soilCdContent" 
-                placeholder="15" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="safetyThreshold" 
-                placeholder="15/1000" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-          </div>
+  <div class="container">
+    <div class="gradient-card">
+      <div class="card-header">
+        <h1>籽粒移除输出通量计算器</h1>
+        <p>评估作物镉含量及籽粒移除对镉的影响,助力农业安全生产</p>
+      </div>
+
+      <div class="instruction-section">
+        <h2>使用说明</h2>
+        <ol>
+          <li>首次使用请点击"作物镉模型计算"按钮。</li>
+          <li>点击"作物镉模型计算"按钮跳转到作物镉模型计算页面。</li>
+          <li>在作物镉模型计算页面完成计算后返回此页面。</li>
+        </ol>
+      </div>
+
+      <div class="crop-cd-model-section">
+        <button class="calculate-btn" @click="openCropCdModelPage">
+          <i class="fas fa-calculator"></i> 作物镉模型计算
+        </button>
+      </div>
+
+      <div class="input-section">
+        <div class="input-group">
+          <label class="label">作物亩产量 (斤)</label>
+          <input v-model="yieldPerMu" placeholder="800" class="custom-input" :disabled="!cropCdModelCalculated" />
         </div>
-        
-        <div class="button-container">
-          <el-button 
-            type="primary" 
-            class="calculate-btn" 
-            :disabled="!inputsEnabled"
-          >
-            计算
-          </el-button>
+
+        <div class="info-panel">
+          <div class="info-item">
+            <i class="fas fa-chart-line"></i>
+            <span>建议范围: 500 - 1500</span>
+          </div>
         </div>
-      </el-form>
-    </el-card>
+      </div>
+
+      <div class="button-section">
+        <button class="calculate-btn" :disabled="!cropCdModelCalculated || !yieldPerMu" @click="onCalculate">
+          <i class="fas fa-calculator"></i> 籽粒移除计算
+        </button>
+      </div>
+
+      <div class="card-footer">
+        <p><i class="fas fa-lightbulb"></i> 提示: 输入数据后点击"籽粒移除计算"按钮获取详细分析</p>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { ref } from 'vue';
+
 export default {
-  data() {
+  name: 'GrainRemovalCalculator',
+  setup() {
+    const cropCdModelCalculated = ref(false);
+    const yieldPerMu = ref(null);
+
+    const openCropCdModelPage = () => {
+      // 这里可以实现跳转到作物镉模型计算页面的逻辑
+      // 例如:window.location.href = '/crop-cd-model';
+      alert('打开作物镉模型计算页面');
+      cropCdModelCalculated.value = true;
+    };
+
+    const onCalculate = () => {
+      if (!yieldPerMu.value) {
+        alert('请输入作物亩产量');
+        return;
+      }
+      alert(`计算完成!当前亩产量: ${yieldPerMu.value} 斤`);
+    };
+
     return {
-      inputsEnabled: false,
-      yieldPerMu: '800',
-      soilCdContent: '0.76',
-      absorptionFactor: '0.0034',
-      soilPH: '0.5',
-      safetyThreshold: '15/1000'
+      cropCdModelCalculated,
+      yieldPerMu,
+      openCropCdModelPage,
+      onCalculate
     };
-  },
-  methods: {
-    enableInputs() {
-      this.inputsEnabled = true;
-    }
   }
 };
 </script>
 
 <style scoped>
-.crop-cd-model-container {
-  padding: 20px;
+body, html {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  overflow: hidden; /* 禁止页面滚动 */
+}
+
+.container {
   display: flex;
   justify-content: center;
+  align-items: center; /* 居中对齐 */
+  height: 100vh;
 }
 
 .gradient-card {
-  /* 半透明渐变背景 */
-  background: linear-gradient(
-    135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
-  );
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  background: linear-gradient(135deg,
+      rgba(250, 253, 255, 0.8),
+      rgba(137, 223, 252, 0.8));
+  border-radius: 20px;
+  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
   padding: 30px;
-  width: 90%;
-  max-width: 800px;
+  width: 100%;
+  max-width: 500px;
+  backdrop-filter: blur(8px);
   border: none;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
-h2 {
-  font-size: 24px;
-  margin-bottom: 20px;
+.card-header {
   text-align: center;
-  color: #333;
-  font-weight: 600;
-  padding-bottom: 15px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
 }
 
-.form-container {
-  margin-top: 25px;
+.card-header h1 {
+  font-size: 2rem;
+  color: #006064;
+  margin-bottom: 10px;
+  font-weight: 700;
+  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
 }
 
-.form-section {
-  display: flex;
-  flex-direction: column;
+.card-header p {
+  font-size: 1.1rem;
+  color: #00838f;
+  opacity: 0.9;
 }
 
-.input-row {
-  display: flex;
-  justify-content: space-between;
+.instruction-section {
+  text-align: center;
   margin-bottom: 20px;
-  flex-wrap: wrap;
 }
 
-.single-input {
-  justify-content: center;
+.instruction-section h2 {
+  font-size: 1.5rem;
+  color: #006064;
+  margin-bottom: 10px;
+}
+
+.instruction-section ol {
+  list-style-type: decimal;
+  padding-left: 20px;
+  margin-bottom: 20px;
+}
+
+.instruction-section li {
+  font-size: 1rem;
+  color: #00838f;
+  margin-bottom: 5px;
 }
 
-.input-item {
-  flex: 0 0 calc(50% - 15px); /* 两个输入框各占50%,减去间距 */
-  margin-bottom: 0;
+.crop-cd-model-section {
+  text-align: center;
+  margin-bottom: 20px;
 }
 
-.single-input .input-item {
-  flex: 0 0 100%; /* 单行输入框占满宽度 */
-  max-width: 500px; /* 限制最大宽度 */
+.input-section {
+  margin: 20px 0;
 }
 
-:deep(.el-form-item__label) {
-  font-size: 16px;
-  font-weight: 500;
-  color: #333;
-  margin-bottom: 8px;
+.input-group {
+  margin-bottom: 20px;
+}
+
+.label {
   display: block;
+  font-weight: 600;
+  font-size: 1.2rem;
+  margin-bottom: 10px;
+  color: #006064;
 }
 
 .custom-input {
   width: 100%;
-}
-
-:deep(.custom-input .el-input__inner) {
+  padding: 15px 10px;
+  border-radius: 12px;
+  border: 2px solid #80deea;
+  font-size: 1rem;
   background: rgba(255, 255, 255, 0.7);
-  border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 12px 15px;
-  font-size: 16px;
-  color: #333;
   transition: all 0.3s ease;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.custom-input:focus {
+  outline: none;
+  border-color: #26c6da;
+  box-shadow: 0 0 0 4px rgba(38, 198, 218, 0.3);
 }
 
-:deep(.custom-input .el-input__inner:disabled) {
-  background: rgba(245, 247, 250, 0.5);
-  color: #a8abb2;
+.custom-input:disabled {
+  background: rgba(255, 255, 255, 0.3);
+  border-color: #ccc;
+  cursor: not-allowed;
 }
 
-.enable-btn, .calculate-btn {
-  width: 100%;
-  max-width: 300px;
-  height: 50px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
-  transition: all 0.4s ease;
-  display: block;
-  margin: 0 auto;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+.info-panel {
+  background: rgba(178, 235, 242, 0.4);
+  border-radius: 12px;
+  padding: 15px;
+  margin-top: 15px;
 }
 
-.enable-btn {
-  margin-top: 10px;
-  margin-bottom: 20px;
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 1rem;
+  color: #006064;
+}
+
+.info-item i {
+  margin-right: 10px;
+  font-size: 1.2rem;
+  color: #00838f;
+}
+
+.button-section {
+  margin: 25px 0;
+  text-align: center;
+  flex-grow: 1;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
 }
 
 .calculate-btn {
-  margin-top: 20px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
+  border: none;
+  border-radius: 50px;
+  padding: 15px 30px;
+  font-size: 1.2rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.4s ease;
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 250px;
+  position: relative;
+  overflow: hidden;
+}
+
+.calculate-btn i {
+  margin-right: 10px;
+  font-size: 1.4rem;
 }
 
-.enable-btn:hover, .calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
+.calculate-btn:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.3);
   background: linear-gradient(to right, #7de8df, #20a03d);
 }
 
-.enable-btn:active, .calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+.calculate-btn:active {
+  transform: translateY(-2px);
 }
 
-.button-container {
-  display: flex;
-  justify-content: center;
+.calculate-btn:disabled {
+  background: gray;
+  cursor: not-allowed;
 }
 
-/* 响应式调整 */
-@media (max-width: 768px) {
-  .input-row {
-    flex-direction: column;
-  }
-  
-  .input-item {
-    flex: 0 0 100%;
-    margin-bottom: 20px;
-  }
-  
+.card-footer {
+  text-align: center;
+  font-size: 1rem;
+  color: #00838f;
+  font-style: italic;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 150, 136, 0.2);
+}
+
+.card-footer i {
+  margin-right: 10px;
+}
+
+/* 水波纹效果 */
+.calculate-btn::after {
+  content: "";
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  transform: translate(-50%, -50%);
+  transition: width 0.6s, height 0.6s;
+}
+
+.calculate-btn:active::after {
+  width: 200px;
+  height: 200px;
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
   .gradient-card {
     padding: 20px;
-  }
-  
-  .enable-btn, .calculate-btn {
     width: 100%;
+    max-width: 100%;
+  }
+
+  .card-header h1 {
+    font-size: 1.8rem;
+  }
+
+  .instruction-section h2 {
+    font-size: 1.4rem;
+  }
+
+  .label {
+    font-size: 1rem;
+  }
+
+  .custom-input {
+    padding: 12px;
+    font-size: 0.9rem;
+  }
+
+  .calculate-btn {
+    padding: 12px 25px;
+    font-size: 1rem;
+    min-width: 200px;
+  }
+
+  .info-item span {
+    font-size: 0.9rem;
   }
 }
-</style>
+</style>
+
+
+

+ 269 - 186
src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue

@@ -1,258 +1,341 @@
 <template>
-  <div class="crop-cd-model-container">
-    <el-card shadow="always" class="gradient-card">
-      <h2>作物镉模型计算</h2>
-      
-      <el-button class="enable-btn" @click="enableInputs">作物镉模型计算</el-button>
-      
-      <el-form label-width="250px" label-position="top" class="form-container">
-        <div class="form-section">
-          <div class="input-row">
-            <el-form-item label="作物亩产量 (斤)" class="input-item">
-              <el-input 
-                v-model="yieldPerMu" 
-                placeholder="800" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-            
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="soilCdContent" 
-                placeholder="0.76" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-          </div>
-          
-          <div class="input-row">
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="absorptionFactor" 
-                placeholder="0.0034" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-            
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="soilPH" 
-                placeholder="0.5" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-          </div>
-          
-          <div class="input-row single-input">
-            <el-form-item label="标题" class="input-item">
-              <el-input 
-                v-model="safetyThreshold" 
-                placeholder="15/1000" 
-                :disabled="!inputsEnabled"
-                class="custom-input"
-              ></el-input>
-            </el-form-item>
-          </div>
+  <div class="container">
+    <div class="gradient-card">
+      <div class="card-header">
+        <h1>秸秆移除含量计算器</h1>
+        <p>评估作物镉含量及秸秆移除的影响,助力农业安全生产</p>
+      </div>
+
+      <div class="instruction-section">
+        <h2>使用说明</h2>
+        <ol>
+          <li>首次使用请点击"作物镉模型计算"按钮。</li>
+          <li>点击"作物镉模型计算"按钮跳转到作物镉模型计算页面。</li>
+          <li>在作物镉模型计算页面完成计算后返回此页面。</li>
+        </ol>
+      </div>
+
+      <div class="crop-cd-model-section">
+        <button class="calculate-btn" @click="openCropCdModelPage">
+          <i class="fas fa-calculator"></i> 作物镉模型计算
+        </button>
+      </div>
+
+      <div class="input-section">
+        <div class="input-group">
+          <label class="label">作物亩产量 (斤)</label>
+          <input v-model="yieldPerMu" placeholder="800" class="custom-input" :disabled="!cropCdModelCalculated" />
         </div>
-        
-        <div class="button-container">
-          <el-button 
-            type="primary" 
-            class="calculate-btn" 
-            :disabled="!inputsEnabled"
-          >
-            计算
-          </el-button>
+
+        <div class="info-panel">
+          <div class="info-item">
+            <i class="fas fa-chart-line"></i>
+            <span>建议范围: 500 - 1500</span>
+          </div>
         </div>
-      </el-form>
-    </el-card>
+      </div>
+
+      <div class="button-section">
+        <button class="calculate-btn" :disabled="!cropCdModelCalculated || !yieldPerMu" @click="onCalculate">
+          <i class="fas fa-calculator"></i> 秸秆移除计算
+        </button>
+      </div>
+
+      <div class="card-footer">
+        <p><i class="fas fa-lightbulb"></i> 提示: 输入数据后点击"秸秆移除计算"按钮获取详细分析</p>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
+import { ref } from 'vue';
+
 export default {
-  data() {
+  name: 'GrainRemovalCalculator',
+  setup() {
+    const cropCdModelCalculated = ref(false);
+    const yieldPerMu = ref(null);
+
+    const openCropCdModelPage = () => {
+      // 这里可以实现跳转到作物镉模型计算页面的逻辑
+      // 例如:window.location.href = '/crop-cd-model';
+      alert('打开作物镉模型计算页面');
+      cropCdModelCalculated.value = true;
+    };
+
+    const onCalculate = () => {
+      if (!yieldPerMu.value) {
+        alert('请输入作物亩产量');
+        return;
+      }
+      alert(`计算完成!当前亩产量: ${yieldPerMu.value} 斤`);
+    };
+
     return {
-      inputsEnabled: false,
-      yieldPerMu: '800',
-      soilCdContent: '0.76',
-      absorptionFactor: '0.0034',
-      soilPH: '0.5',
-      safetyThreshold: '15/1000'
+      cropCdModelCalculated,
+      yieldPerMu,
+      openCropCdModelPage,
+      onCalculate
     };
-  },
-  methods: {
-    enableInputs() {
-      this.inputsEnabled = true;
-    }
   }
 };
 </script>
 
 <style scoped>
-.crop-cd-model-container {
-  padding: 20px;
+body,
+html {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  overflow: hidden;
+  /* 禁止页面滚动 */
+}
+
+.container {
   display: flex;
   justify-content: center;
+  align-items: center;
+  /* 居中对齐 */
+  height: 100vh;
 }
 
 .gradient-card {
-  /* 半透明渐变背景 */
-  background: linear-gradient(
-    135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
-  );
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  background: linear-gradient(135deg,
+      rgba(250, 253, 255, 0.8),
+      rgba(137, 223, 252, 0.8));
+  border-radius: 20px;
+  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
   padding: 30px;
-  width: 90%;
-  max-width: 800px; /* 增加最大宽度以容纳两列 */
+  width: 100%;
+  max-width: 500px;
+  backdrop-filter: blur(8px);
   border: none;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
-h2 {
-  font-size: 24px;
-  margin-bottom: 20px;
+.card-header {
   text-align: center;
-  color: #333;
-  font-weight: 600;
-  padding-bottom: 15px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
 }
 
-.form-container {
-  margin-top: 25px;
+.card-header h1 {
+  font-size: 2rem;
+  color: #006064;
+  margin-bottom: 10px;
+  font-weight: 700;
+  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
 }
 
-.form-section {
-  display: flex;
-  flex-direction: column;
+.card-header p {
+  font-size: 1.1rem;
+  color: #00838f;
+  opacity: 0.9;
 }
 
-.input-row {
-  display: flex;
-  justify-content: space-between;
+.instruction-section {
+  text-align: center;
   margin-bottom: 20px;
-  flex-wrap: wrap;
 }
 
-.input-item {
-  flex: 0 0 calc(50% - 15px); /* 两个输入框各占50%,减去间距 */
-  margin-bottom: 0;
+.instruction-section h2 {
+  font-size: 1.5rem;
+  color: #006064;
+  margin-bottom: 10px;
 }
 
-.single-input {
-  justify-content: center;
+.instruction-section ol {
+  list-style-type: decimal;
+  padding-left: 20px;
+  margin-bottom: 20px;
+}
+
+.instruction-section li {
+  font-size: 1rem;
+  color: #00838f;
+  margin-bottom: 5px;
 }
 
-.single-input .input-item {
-  flex: 0 0 100%; /* 单行输入框占满宽度 */
-  max-width: 500px; /* 限制最大宽度 */
+.crop-cd-model-section {
+  text-align: center;
+  margin-bottom: 20px;
 }
 
-:deep(.el-form-item__label) {
-  font-size: 16px;
-  font-weight: 500;
-  color: #333;
-  margin-bottom: 8px;
+.input-section {
+  margin: 20px 0;
+}
+
+.input-group {
+  margin-bottom: 20px;
+}
+
+.label {
   display: block;
+  font-weight: 600;
+  font-size: 1.2rem;
+  margin-bottom: 10px;
+  color: #006064;
 }
 
 .custom-input {
   width: 100%;
-}
-
-:deep(.custom-input .el-input__inner) {
+  padding: 15px 10px;
+  border-radius: 12px;
+  border: 2px solid #80deea;
+  font-size: 1rem;
   background: rgba(255, 255, 255, 0.7);
-  border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 12px 15px;
-  font-size: 16px;
-  color: #333;
   transition: all 0.3s ease;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.custom-input:focus {
+  outline: none;
+  border-color: #26c6da;
+  box-shadow: 0 0 0 4px rgba(38, 198, 218, 0.3);
 }
 
-:deep(.custom-input .el-input__inner:disabled) {
-  background: rgba(245, 247, 250, 0.5);
-  color: #a8abb2;
+.custom-input:disabled {
+  background: rgba(255, 255, 255, 0.3);
+  border-color: #ccc;
+  cursor: not-allowed;
 }
 
-.enable-btn, .calculate-btn {
-  width: 100%;
-  max-width: 300px;
-  height: 50px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
-  transition: all 0.4s ease;
-  display: block;
-  margin: 0 auto;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+.info-panel {
+  background: rgba(178, 235, 242, 0.4);
+  border-radius: 12px;
+  padding: 15px;
+  margin-top: 15px;
 }
 
-.enable-btn {
-  margin-top: 10px;
-  margin-bottom: 20px;
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 1rem;
+  color: #006064;
+}
+
+.info-item i {
+  margin-right: 10px;
+  font-size: 1.2rem;
+  color: #00838f;
+}
+
+.button-section {
+  margin: 25px 0;
+  text-align: center;
+  flex-grow: 1;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
 }
 
 .calculate-btn {
-  margin-top: 20px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
+  border: none;
+  border-radius: 50px;
+  padding: 15px 30px;
+  font-size: 1.2rem;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.4s ease;
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 250px;
+  position: relative;
+  overflow: hidden;
 }
 
-.enable-btn:hover, .calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
+.calculate-btn i {
+  margin-right: 10px;
+  font-size: 1.4rem;
+}
+
+.calculate-btn:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.3);
   background: linear-gradient(to right, #7de8df, #20a03d);
 }
 
-.enable-btn:active, .calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+.calculate-btn:active {
+  transform: translateY(-2px);
 }
 
-.button-container {
-  display: flex;
-  justify-content: center;
+.calculate-btn:disabled {
+  background: gray;
+  cursor: not-allowed;
 }
 
-/* 响应式调整 */
-@media (max-width: 768px) {
-  .input-row {
-    flex-direction: column;
-  }
-  
-  .input-item {
-    flex: 0 0 100%;
-    margin-bottom: 20px;
-  }
-  
+.card-footer {
+  text-align: center;
+  font-size: 1rem;
+  color: #00838f;
+  font-style: italic;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 150, 136, 0.2);
+}
+
+.card-footer i {
+  margin-right: 10px;
+}
+
+/* 水波纹效果 */
+.calculate-btn::after {
+  content: "";
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  transform: translate(-50%, -50%);
+  transition: width 0.6s, height 0.6s;
+}
+
+.calculate-btn:active::after {
+  width: 200px;
+  height: 200px;
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
   .gradient-card {
     padding: 20px;
-  }
-  
-  .enable-btn, .calculate-btn {
     width: 100%;
+    max-width: 100%;
+  }
+
+  .card-header h1 {
+    font-size: 1.8rem;
+  }
+
+  .instruction-section h2 {
+    font-size: 1.4rem;
+  }
+
+  .label {
+    font-size: 1rem;
+  }
+
+  .custom-input {
+    padding: 12px;
+    font-size: 0.9rem;
+  }
+
+  .calculate-btn {
+    padding: 12px 25px;
+    font-size: 1rem;
+    min-width: 200px;
+  }
+
+  .info-item span {
+    font-size: 0.9rem;
   }
 }
-</style>
+</style>

+ 225 - 77
src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue

@@ -1,118 +1,266 @@
 <template>
-  <div class="leakage-container">
-    <el-card class="gradient-card" shadow="hover">
-      <el-row :gutter="20">
-        <el-col :span="24">
-          <p class="label">地下渗漏(g/ha/a)</p>
-        </el-col>
-        <el-col :span="24">
-          <el-input
-            v-model="leakage"
-            placeholder="请输入"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="24" style="margin-top: 20px;">
-          <el-button class="calculate-btn" @click="onCalculate">计算</el-button>
-        </el-col>
-      </el-row>
-    </el-card>
+  <div class="container">
+    <div class="gradient-card">
+      <div class="card-header">
+        <h1>地下渗漏计算器</h1>
+        <p>评估地下水渗漏量及其环境影响</p>
+      </div>
+
+      <div class="card-content">
+        <div class="input-section">
+          <div class="input-group">
+            <label class="label">地下渗漏 (g/ha/a)</label>
+            <input v-model="leakage" placeholder="请输入地表径流" class="custom-input" />
+          </div>
+
+          <div class="info-panel">
+            <div class="info-item">
+              <i class="fas fa-info-circle"></i>
+              <span>当前值: {{ leakage || '0.023' }}</span>
+            </div>
+            <div class="info-item">
+              <i class="fas fa-chart-line"></i>
+              <span>建议范围: 0.01 - 0.05</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="button-section">
+          <button class="calculate-btn" @click="onCalculate">
+            <i class="fas fa-calculator"></i> 计算地表径流
+          </button>
+        </div>
+      </div>
+
+      <div class="card-footer">
+        <p><i class="fas fa-lightbulb"></i> 提示: 输入地表径流后点击计算按钮获取详细分析</p>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup>
+<script>
 import { ref } from 'vue';
-import { ElCard, ElRow, ElCol, ElInput, ElButton } from 'element-plus';
 
-const leakage = ref('0.023');
+export default {
+  name: 'LeakageCalculator',
+  setup() {
+    const leakage = ref('0.023');
 
-const onCalculate = () => {
-  // 暂无计算逻辑,仅作展示
-  alert('计算按钮已点击');
+    const onCalculate = () => {
+      alert(`计算完成!当前地表径流: ${leakage.value || '0.023'} g/ha/a`);
+    };
+
+    return {
+      leakage,
+      onCalculate
+    };
+  }
 };
 </script>
 
 <style scoped>
-.leakage-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  padding: 20px;
+body, html {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  overflow: hidden; /* 禁止页面滚动 */
 }
 
 .gradient-card {
-  /* 半透明渐变背景 */
-  background: linear-gradient(
-    135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
-  );
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  background: linear-gradient(135deg,
+      rgba(250, 253, 255, 0.8),
+      rgba(137, 223, 252, 0.8));
+  border-radius: 20px;
+  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
   padding: 30px;
-  text-align: left; /* 改为左对齐 */
-  width: 300px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  width: 100%;
+  max-width: 500px;
+  margin-left: 350px; /* 左侧留出空间 */
+  backdrop-filter: blur(8px);
   border: none;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.card-header {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.card-header h1 {
+  font-size: 2rem;
+  color: #006064;
+  margin-bottom: 10px;
+  font-weight: 700;
+  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.card-header p {
+  font-size: 1.1rem;
+  color: #00838f;
+  opacity: 0.9;
+}
+
+.input-section {
+  margin: 20px 0;
+}
+
+.input-group {
+  margin-bottom: 20px;
 }
 
 .label {
-  font-weight: bold;
-  font-size: 18px;
-  margin-bottom: 10px; /* 减少底部外边距 */
-  color: #333;
+  display: block;
+  font-weight: 600;
+  font-size: 1.2rem;
+  margin-bottom: 10px;
+  color: #006064;
 }
 
 .custom-input {
   width: 100%;
-  max-width: 200px;
-  margin-left: 0; /* 确保输入框靠左对齐 */
+  padding: 15px 10px;
+  border-radius: 12px;
+  border: 2px solid #80deea;
+  font-size: 1rem;
+  background: rgba(255, 255, 255, 0.7);
+  transition: all 0.3s ease;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
 }
 
-/* 自定义输入框样式 */
-:deep(.custom-input .el-input__inner) {
-  background: rgba(255, 255, 255, 0.7);
-  border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 10px 15px;
-  font-size: 16px;
-  color: #333;
+.custom-input:focus {
+  outline: none;
+  border-color: #26c6da;
+  box-shadow: 0 0 0 4px rgba(38, 198, 218, 0.3);
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.info-panel {
+  background: rgba(178, 235, 242, 0.4);
+  border-radius: 12px;
+  padding: 15px;
+  margin-top: 15px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 1rem;
+  color: #006064;
+}
+
+.info-item i {
+  margin-right: 10px;
+  font-size: 1.2rem;
+  color: #00838f;
+}
+
+.button-section {
+  margin: 25px 0;
+  text-align: center;
+  flex-grow: 1;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
 }
 
 .calculate-btn {
-  width: 100%;
-  max-width: 200px;
-  height: 50px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
   border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
+  border-radius: 50px;
+  padding: 15px 30px;
+  font-size: 1.2rem;
+  font-weight: 600;
+  cursor: pointer;
   transition: all 0.4s ease;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 250px;
+  position: relative;
+  overflow: hidden;
+}
+
+.calculate-btn i {
+  margin-right: 10px;
+  font-size: 1.4rem;
 }
 
 .calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
+  transform: translateY(-5px);
+  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.3);
   background: linear-gradient(to right, #7de8df, #20a03d);
 }
 
 .calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+  transform: translateY(-2px);
+}
+
+.card-footer {
+  text-align: center;
+  font-size: 1rem;
+  color: #00838f;
+  font-style: italic;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 150, 136, 0.2);
+}
+
+.card-footer i {
+  margin-right: 10px;
+}
+
+/* 水波纹效果 */
+.calculate-btn::after {
+  content: "";
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  transform: translate(-50%, -50%);
+  transition: width 0.6s, height 0.6s;
+}
+
+.calculate-btn:active::after {
+  width: 200px;
+  height: 200px;
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
+  .gradient-card {
+    padding: 20px;
+    width: 100%;
+    max-width: 100%;
+  }
+
+  .card-header h1 {
+    font-size: 1.8rem;
+  }
+
+  .label {
+    font-size: 1rem;
+  }
+
+  .custom-input {
+    padding: 12px;
+    font-size: 0.9rem;
+  }
+
+  .calculate-btn {
+    padding: 12px 25px;
+    font-size: 1rem;
+    min-width: 200px;
+  }
+
+  .info-item span {
+    font-size: 0.9rem;
+  }
 }
 </style>

+ 228 - 78
src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue

@@ -1,119 +1,269 @@
 <template>
-  <div class="runoff-container">
-    <el-card class="gradient-card" shadow="hover">
-      <el-row :gutter="20">
-        <el-col :span="24">
-          <p class="label">地表径流(g/ha/a)</p>
-        </el-col>
-        <el-col :span="24">
-          <el-input
-            v-model="runoff"
-            placeholder="请输入"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="24" style="margin-top: 20px;">
-          <el-button class="calculate-btn" @click="onCalculate">计算</el-button>
-        </el-col>
-      </el-row>
-    </el-card>
+  <div class="container">
+    <div class="gradient-card">
+      <div class="card-header">
+        <h1>地表径流计算器</h1>
+        <p>评估地表径流量及其环境影响</p>
+      </div>
+
+      <div class="card-content">
+        <div class="input-section">
+          <div class="input-group">
+            <label class="label">地表径流 (g/ha/a)</label>
+            <input v-model="leakage" placeholder="请输入地表径流" class="custom-input" />
+          </div>
+
+          <div class="info-panel">
+            <div class="info-item">
+              <i class="fas fa-info-circle"></i>
+              <span>当前值: {{ leakage || '0.368' }}</span>
+            </div>
+            <div class="info-item">
+              <i class="fas fa-chart-line"></i>
+              <span>建议范围: 0.01 - 0.05</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="button-section">
+          <button class="calculate-btn" @click="onCalculate">
+            <i class="fas fa-calculator"></i> 计算地表径流
+          </button>
+        </div>
+      </div>
+
+      <div class="card-footer">
+        <p><i class="fas fa-lightbulb"></i> 提示: 输入地表径流后点击计算按钮获取详细分析</p>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup>
+<script>
 import { ref } from 'vue';
-import { ElCard, ElRow, ElCol, ElInput, ElButton } from 'element-plus';
 
-const runoff = ref('0.368');
+export default {
+  name: 'LeakageCalculator',
+  setup() {
+    const leakage = ref('0.368');
 
-const onCalculate = () => {
-  // 暂无计算逻辑,仅作展示
-  alert('计算按钮已点击');
+    const onCalculate = () => {
+      alert(`计算完成!当前地表径流: ${leakage.value || '0.368'} g/ha/a`);
+    };
+
+    return {
+      leakage,
+      onCalculate
+    };
+  }
 };
 </script>
 
 <style scoped>
-.runoff-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  padding: 20px;
-  margin-left: 10px; /* 确保输入框靠左对齐 */
+body, html {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  overflow: hidden; /* 禁止页面滚动 */
 }
 
 .gradient-card {
-  /* 半透明渐变背景 */
-  background: linear-gradient(
-    135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
-  );
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+  background: linear-gradient(135deg,
+      rgba(250, 253, 255, 0.8),
+      rgba(137, 223, 252, 0.8));
+  border-radius: 20px;
+  box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
   padding: 30px;
-  text-align: left; /* 改为左对齐 */
-  width: 300px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  width: 100%;
+  max-width: 500px;
+  margin-left: 350px; /* 左侧留出空间 */
+  backdrop-filter: blur(8px);
   border: none;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.card-header {
+  text-align: center;
+  margin-bottom: 20px;
+}
+
+.card-header h1 {
+  font-size: 2rem;
+  color: #006064;
+  margin-bottom: 10px;
+  font-weight: 700;
+  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.card-header p {
+  font-size: 1.1rem;
+  color: #00838f;
+  opacity: 0.9;
+}
+
+.input-section {
+  margin: 20px 0;
+}
+
+.input-group {
+  margin-bottom: 20px;
 }
 
 .label {
-  font-weight: bold;
-  font-size: 18px;
-  margin-bottom: 10px; /* 减少底部外边距 */
-  color: #333;
+  display: block;
+  font-weight: 600;
+  font-size: 1.2rem;
+  margin-bottom: 10px;
+  color: #006064;
 }
 
 .custom-input {
   width: 100%;
-  max-width: 200px;
-  margin-left: 10px; /* 确保输入框靠左对齐 */
+  padding: 15px 10px;
+  border-radius: 12px;
+  border: 2px solid #80deea;
+  font-size: 1rem;
+  background: rgba(255, 255, 255, 0.7);
+  transition: all 0.3s ease;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
 }
 
-/* 自定义输入框样式 */
-:deep(.custom-input .el-input__inner) {
-  background: rgba(255, 255, 255, 0.7);
-  border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 10px 15px;
-  font-size: 16px;
+.custom-input:focus {
+  outline: none;
+  border-color: #26c6da;
+  box-shadow: 0 0 0 4px rgba(38, 198, 218, 0.3);
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.info-panel {
+  background: rgba(178, 235, 242, 0.4);
+  border-radius: 12px;
+  padding: 15px;
+  margin-top: 15px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  font-size: 1rem;
+  color: #006064;
+}
+
+.info-item i {
+  margin-right: 10px;
+  font-size: 1.2rem;
+  color: #00838f;
+}
+
+.button-section {
+  margin: 25px 0;
+  text-align: center;
+  flex-grow: 1;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
 }
 
 .calculate-btn {
-  width: 100%;
-  max-width: 200px;
-  height: 50px;
+  background: linear-gradient(to right, #8DF9F0, #26B046);
+  color: white;
   border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
+  border-radius: 50px;
+  padding: 15px 30px;
+  font-size: 1.2rem;
+  font-weight: 600;
+  cursor: pointer;
   transition: all 0.4s ease;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 250px;
+  position: relative;
+  overflow: hidden;
+}
+
+.calculate-btn i {
+  margin-right: 10px;
+  font-size: 1.4rem;
 }
 
 .calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
+  transform: translateY(-5px);
+  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.3);
   background: linear-gradient(to right, #7de8df, #20a03d);
 }
 
 .calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+  transform: translateY(-2px);
 }
 
-</style>
+.card-footer {
+  text-align: center;
+  font-size: 1rem;
+  color: #00838f;
+  font-style: italic;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 150, 136, 0.2);
+}
+
+.card-footer i {
+  margin-right: 10px;
+}
+
+/* 水波纹效果 */
+.calculate-btn::after {
+  content: "";
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  background: rgba(255, 255, 255, 0.3);
+  border-radius: 50%;
+  transform: translate(-50%, -50%);
+  transition: width 0.6s, height 0.6s;
+}
+
+.calculate-btn:active::after {
+  width: 200px;
+  height: 200px;
+}
+
+/* 响应式设计 */
+@media (max-width: 480px) {
+  .gradient-card {
+    padding: 20px;
+    width: 100%;
+    max-width: 100%;
+  }
+
+  .card-header h1 {
+    font-size: 1.8rem;
+  }
+
+  .label {
+    font-size: 1rem;
+  }
+
+  .custom-input {
+    padding: 12px;
+    font-size: 0.9rem;
+  }
+
+  .calculate-btn {
+    padding: 12px 25px;
+    font-size: 1rem;
+    min-width: 200px;
+  }
+
+  .info-item span {
+    font-size: 0.9rem;
+  }
+}
+</style>
+
+
+

+ 203 - 89
src/views/User/selectCityAndCounty.vue

@@ -3,7 +3,7 @@
     <!-- 左侧背景图容器 -->
     <div class="background-container">
       <h1 class="title">选择市县及地区</h1>
-      <!-- 城市按钮容器放在背景图内 -->
+      <!-- 城市按钮容器 -->
       <div class="city-buttons-container">
         <el-button
           v-for="city in cities"
@@ -34,14 +34,21 @@
                 :class="{ selected: isDistrictSelected(district.name) }"
                 @click="toggleDistrict(district.name)"
               >
-                <img :src="district.image" class="district-image" />
+                <!-- 左侧图片区域 -->
+                <div class="image-container">
+                  <img :src="district.image" class="district-image" />
+                </div>
+                
+                <!-- 右侧内容区域 - 优化布局 -->
                 <div class="district-content">
-                  <span class="district-name">{{ district.name }}</span>
-                  <p class="district-desc">{{ district.description }}</p>
+                  <div class="text-content">
+                    <span class="district-name">{{ district.name }}</span>
+                    <p class="district-desc">{{ district.description || "该地区详细信息" }}</p>
+                  </div>
+                  <el-icon class="arrow-icon">
+                    <arrow-right />
+                  </el-icon>
                 </div>
-                <el-icon class="arrow-icon">
-                  <arrow-right />
-                </el-icon>
               </div>
             </div>
           </div>
@@ -80,39 +87,39 @@ export default {
 
     const districts = {
       韶关: [
-        { name: "武江区", image: "/images/武江区.jpg" },
-        { name: "浈江区", image: "/images/浈江区.jpg" },
-        { name: "曲江区", image: "/images/曲江区.jpg" },
-        { name: "始兴县", image: "/images/始兴县.jpg" },
-        { name: "仁化县", deimage: "/images/仁化县.jpg" },
-        { name: "翁源县", image: "/images/翁源县.jpg" },
-        { name: "乳源瑶族自治县", image: "/images/乳源瑶族自治县.jpg" },
-        { name: "新丰县", image: "/images/新丰县.jpg" },
-        { name: "乐昌市", image: "/images/乐昌市.jpg" },
-        { name: "南雄市", image: "/images/南雄市.jpg" }
+        { name: "武江区", image: "/images/武江区.jpg", description: "位于韶关市西部,工业发达,环境优美" },
+        { name: "浈江区", image: "/images/浈江区.jpg", description: "韶关市中心城区,商业繁荣,交通便利" },
+        { name: "曲江区", image: "/images/曲江区.jpg", description: "历史文化名城,旅游资源丰富" },
+        { name: "始兴县", image: "/images/始兴县.jpg", description: "生态环境优良,农业发达" },
+        { name: "仁化县", image: "/images/仁化县.jpg", description: "丹霞地貌核心区,世界自然遗产" },
+        { name: "翁源县", image: "/images/翁源县.jpg", description: "中国兰花之乡,生态农业示范区" },
+        { name: "乳源瑶族自治县", image: "/images/乳源瑶族自治县.jpg", description: "瑶族文化发源地,自然风光秀丽" },
+        { name: "新丰县", image: "/images/新丰县.jpg", description: "生态旅游胜地,温泉资源丰富" },
+        { name: "乐昌市", image: "/images/乐昌市.jpg", description: "粤北门户城市,农业特色鲜明" },
+        { name: "南雄市", image: "/images/南雄市.jpg", description: "历史文化名城,银杏之乡" }
       ],
       河池: [
-        { name: "金城江区", image: "/images/金城江区.jpg" },
-        { name: "宜州区", image: "/images/宜州区.jpg" },
-        { name: "南丹县", image: "/images/南丹县.jpg" },
-        { name: "天峨县", image: "/images/天峨县.jpg" },
-        { name: "凤山县", image: "/images/凤山县.jpg" },
-        { name: "东兰县", image: "/images/东兰县.jpg" },
-        { name: "罗城仫佬族自治县", image: "/images/罗城仫佬族自治县.jpg" },
-        { name: "环江毛南族自治县", image: "/images/环江毛南族自治县.jpg" },
-        { name: "巴马瑶族自治县", image: "/images/巴马瑶族自治县.png" },
-        { name: "都安瑶族自治县", image: "/images/都安瑶族自治县.jpg" },
-        { name: "大化瑶族自治县", image: "/images/大化瑶族自治县.jpg" }
+        { name: "金城江区", image: "/images/金城江区.jpg", description: "河池市中心城区,交通枢纽,商业中心" },
+        { name: "宜州区", image: "/images/宜州区.jpg", description: "刘三姐故乡,壮族文化发源地" },
+        { name: "南丹县", image: "/images/南丹县.jpg", description: "有色金属之乡,白裤瑶文化中心" },
+        { name: "天峨县", image: "/images/天峨县.jpg", description: "红水河上游,森林覆盖率高达85%" },
+        { name: "凤山县", image: "/images/凤山县.jpg", description: "世界地质公园,喀斯特地貌典型区" },
+        { name: "东兰县", image: "/images/东兰县.jpg", description: "革命老区,长寿之乡" },
+        { name: "罗城仫佬族自治县", image: "/images/罗城仫佬族自治县.jpg", description: "全国唯一的仫佬族自治县" },
+        { name: "环江毛南族自治县", image: "/images/环江毛南族自治县.jpg", description: "世界自然遗产地,喀斯特地貌典型" },
+        { name: "巴马瑶族自治县", image: "/images/巴马瑶族自治县.png", description: "世界长寿之乡,养生旅游胜地" },
+        { name: "都安瑶族自治县", image: "/images/都安瑶族自治县.jpg", description: "喀斯特地貌典型区,地下河之乡" },
+        { name: "大化瑶族自治县", image: "/images/大化瑶族自治县.jpg", description: "红水河畔明珠,水电资源丰富" }
       ],
       腾冲: [
-        { name: "腾冲市区", image: "/images/腾冲市区.jpg" },
-        { name: "和顺古镇", image: "/images/和顺古镇.jpg" },
-        { name: "固东镇", image: "/images/固东镇.jpg" },
-        { name: "界头镇", image: "/images/界头镇.jpg" },
-        { name: "曲石镇", image: "/images/曲石镇.jpg" },
-        { name: "明光镇", image: "/images/明光镇.jpg" },
-        { name: "清水乡", image: "/images/清水乡.jpg" },
-        { name: "猴桥镇", image: "/images/猴桥镇.jpg" }
+        { name: "腾冲市区", image: "/images/腾冲市区.jpg", description: "云南边陲重镇,历史文化名城" },
+        { name: "和顺古镇", image: "/images/和顺古镇.jpg", description: "中国第一魅力名镇,侨乡文化代表" },
+        { name: "固东镇", image: "/images/固东镇.jpg", description: "银杏村所在地,秋季摄影胜地" },
+        { name: "界头镇", image: "/images/界头镇.jpg", description: "高黎贡山脚下,油菜花海之乡" },
+        { name: "曲石镇", image: "/images/曲石镇.jpg", description: "火山地质奇观,黑鱼河风景区" },
+        { name: "明光镇", image: "/images/明光镇.jpg", description: "边境贸易重镇,多元文化交融" },
+        { name: "清水乡", image: "/images/清水乡.jpg", description: "温泉度假胜地,热海景区所在地" },
+        { name: "猴桥镇", image: "/images/猴桥镇.jpg", description: "中缅边境口岸,跨境贸易枢纽" }
       ],
     };
 
@@ -122,34 +129,30 @@ export default {
 
     const selectCity = (cityName) => {
       selectedCity.value = cityName;
-      selectedDistricts.value = []; // 切换城市时清空所选项
+      selectedDistricts.value = [];
     };
     
-    // 切换地区选择状态
     const toggleDistrict = (districtName) => {
       const index = selectedDistricts.value.indexOf(districtName);
       if (index >= 0) {
-        // 如果已选中,则移除
         selectedDistricts.value.splice(index, 1);
       } else {
-        // 如果未选中,则添加
         selectedDistricts.value.push(districtName);
       }
     };
     
-    // 检查地区是否被选中
     const isDistrictSelected = (districtName) => {
       return selectedDistricts.value.includes(districtName);
     };
 
     const goToKanBan = () => {
       localStorage.setItem("selectedDistricts", JSON.stringify(selectedDistricts.value));
-      const userType = localStorage.getItem("userType"); // 获取用户类型
+      const userType = localStorage.getItem("userType");
       if (userType === "admin") {
-        router.push({ name: "parameterConfig" }); // 管理员跳转到 parameterConfig
+        router.push({ name: "parameterConfig" });
       } else {
         router.push({
-          name: "samplingMethodDevice1", // 普通用户跳转到 samplingMethodDevice1
+          name: "samplingMethodDevice1",
           query: { districts: selectedDistricts.value.join(",") },
         });
       }
@@ -176,8 +179,9 @@ export default {
   top: 0;
   left: 0;
   width: 300px;
-  height: 100vh;
-  background-image: url('@/assets/city-bg.jpg');
+  height: 100%;
+  min-height: 100vh;
+  background-image: url('@/assets/city-bg.jpg'); /* 添加背景图 */
   background-size: cover;
   background-position: center;
   z-index: 0;
@@ -185,6 +189,7 @@ export default {
   flex-direction: column;
   align-items: center;
   padding-top: 40px;
+  box-shadow: 5px 0 15px rgba(0, 0, 0, 0.1);
 }
 
 .city-selection {
@@ -193,24 +198,27 @@ export default {
   position: relative;
 }
 
+/* 右侧内容区域 - 移除背景图 */
 .content-area {
   margin-left: 300px;
   flex: 1;
   padding: 20px;
   display: flex;
   flex-direction: column;
-  height: 100vh;
+  min-height: 100vh;
   box-sizing: border-box;
+  background: linear-gradient(to bottom, #FFECB8 0%, #EBEBEB 100%); /* 添加渐变色背景 */
 }
 
+
 .title {
   color: #fff;
   font-weight: 700;
   font-size: 36px;
   margin-bottom: 60px;
   text-align: center;
-  text-shadow: 0 2px 4px rgba(0,0,0,0.5);
   padding: 0 20px;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
 }
 
 /* 城市按钮容器 */
@@ -219,30 +227,37 @@ export default {
   flex-direction: column;
   gap: 35px;
   width: 80%;
-  padding: 90px 70px 0 30px;
+  padding: 90px 70px 0 50px;
 }
 
+.city-button {
+  position: relative; /* 添加相对定位 */
+}
+
+.city-button:first-child {
+  left: 9px; /* 第一个按钮向右移动5px */
+}
 .city-button {
   width: 100%;
   height: 80px;
   font-size: 28px;
   border: none;
   border-radius: 15px;
-  background-color: rgba(255, 255, 255, 0.3);
-  color: #fff;
+  background-color: rgba(255, 255, 255, 0.4);
+  color: #333;
   transition: all 0.3s ease;
   cursor: pointer;
-  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
-  backdrop-filter: blur(5px);
+  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
 }
 
 .city-button:hover {
-  background-color: rgba(255, 255, 255, 0.5);
+  background-color: rgba(255, 255, 255, 0.6);
 }
 
+/* 加深选中状态 */
 .city-button.selected {
-  background-color: rgba(18, 102, 252, 0.8);
-  color: #E9FEFF;
+  background-color: #B36F00;
+  color: #FFFFFF;
   box-shadow: 0 6px 12px rgba(0,0,0,0.3);
 }
 
@@ -290,52 +305,83 @@ export default {
 .district-card {
   width: 95%;
   height: 150px;
-  background-color: #B5DBF0;
+  background: linear-gradient(180deg, #FFFFFF 0%, #FFFACD 100%);
   border-radius: 12px;
   display: flex;
-  align-items: center;
-  padding: 20px;
   box-shadow: 0 4px 15px rgba(0,0,0,0.1);
   cursor: pointer;
   transition: all 0.3s ease;
   position: relative;
-  border: 2px solid transparent; /* 默认透明边框 */
+  overflow: hidden;
+  border: 2px solid transparent;
 }
 
 /* 选中状态的卡片样式 */
 .district-card.selected {
-  border-color: #1266FC;
-  background-color: #a1c3e6;
-  box-shadow: 0 6px 20px rgba(18, 102, 252, 0.3);
+  box-shadow: 0 6px 20px rgba(179, 111, 0, 0.2);
+  border: 2px solid #E6B559;
 }
 
 .district-card:hover {
-  background-color: #a1c3e6;
-  transform: translateY(-5px);
-  box-shadow: 0 6px 20px rgba(0,0,0,0.15);
+  transform: translateY(-3px);
+  box-shadow: 0 6px 15px rgba(0,0,0,0.1);
 }
 
-.district-image {
+/* 左侧图片容器 */
+.image-container {
   width: 170px;
+  height: 150px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(180deg, #FFFFFF 0%, #FFFACD 100%);
+  border-radius: 12px 0 0 12px;
+  overflow: hidden;
+}
+
+/* 选中状态左侧图片区域背景色 */
+.district-card.selected .image-container {
+  background: linear-gradient(180deg, #FFFBE6 0%, #FFE8B8 100%);
+}
+
+.district-card.selected:hover .image-container {
+  background: linear-gradient(180deg, #FFF6D9 0%, #FFE1A6 100%);
+}
+
+.district-image {
+  width: 160px;
   height: 110px;
   object-fit: cover;
   border-radius: 8px;
-  margin-right: 20px;
-  flex-shrink: 0;
+  border: 1px solid rgba(255, 255, 255, 0.2);
 }
 
+/* 右侧内容区域 - 优化垂直居中 */
 .district-content {
+  flex: 1;
+  display: flex;
+  padding: 20px;
+  position: relative;
+  background: linear-gradient(180deg, #FFFFFF 0%, #FFFACD 100%);
+  border-radius: 0 12px 12px 0;
+  align-items: center; /* 垂直居中 */
+}
+
+/* 文字内容容器 */
+.text-content {
   flex: 1;
   display: flex;
   flex-direction: column;
-  overflow: hidden;
+  justify-content: center; /* 垂直居中 */
+  padding-right: 30px; /* 为箭头留出空间 */
 }
 
+/* 未选中状态文字颜色 */
 .district-name {
   font-size: 22px;
   font-weight: 700;
-  color: #222;
-  margin-bottom: 10px;
+  color: #333333;
+  margin-bottom: 8px;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -343,24 +389,43 @@ export default {
 
 .district-desc {
   font-size: 16px;
-  color: #444;
+  color: #666666;
   line-height: 1.4;
   display: -webkit-box;
   -webkit-line-clamp: 2;
+  line-clamp: 2;
   -webkit-box-orient: vertical;
   overflow: hidden;
   text-overflow: ellipsis;
-  flex-grow: 1;
 }
 
+/* 选中状态文字颜色 */
+.district-card.selected .district-name,
+.district-card.selected .district-desc {
+  color: #333333;
+  font-weight: bold;
+}
+
+/* 箭头图标垂直居中 */
 .arrow-icon {
+  position: absolute;
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
   font-size: 24px;
-  color: #1266FC;
-  margin-left: 15px;
-  flex-shrink: 0;
+  color: #333;
+  transition: transform 0.3s ease;
 }
 
-/* 按钮容器 - 固定在底部 */
+.district-card:hover .arrow-icon {
+  transform: translateY(-50%) translateX(3px);
+}
+
+.district-card.selected .arrow-icon {
+  color: #E6B559;
+}
+
+/* 按钮容器 */
 .button-container {
   padding: 20px 0;
   text-align: center;
@@ -369,29 +434,32 @@ export default {
   bottom: 20px;
   z-index: 10;
   border-radius: 10px;
-  margin-top: auto; /* 确保按钮在底部 */
+  margin-top: auto;
 }
 
+/* 跳转按钮 */
 .go-button {
-  width: 200px;
+  width: 220px;
   height: 60px;
   font-size: 20px;
   border-radius: 30px;
-  margin-right: -1130px;
-  background: linear-gradient(to right, #1266FC, #4e8cff);
+  margin-right: -1080px;
+  background: linear-gradient(135deg, #E6B559 0%, #DBAD55 100%);
+  color: #fff;
+  box-shadow: 0 4px 12px rgba(219, 173, 85, 0.4);
   border: none;
-  box-shadow: 0 6px 15px rgba(18, 102, 252, 0.4);
   transition: all 0.3s ease;
 }
 
 .go-button:hover {
   transform: scale(1.05);
-  box-shadow: 0 8px 20px rgba(18, 102, 252, 0.6);
+  box-shadow: 0 6px 15px rgba(219, 173, 85, 0.6);
 }
 
 .go-button:disabled {
   opacity: 0.6;
   cursor: not-allowed;
+  background: linear-gradient(135deg, #c0a47f 0%, #b5a189 100%);
 }
 
 /* 滚动条样式 */
@@ -400,21 +468,67 @@ export default {
 }
 
 .districts-group::-webkit-scrollbar-track {
-  background: #f1f1f1;
+  background: rgba(241, 241, 241, 0.5);
   border-radius: 4px;
 }
 
 .districts-group::-webkit-scrollbar-thumb {
-  background: #888;
+  background: rgba(136, 136, 136, 0.7);
   border-radius: 4px;
 }
 
 .districts-group::-webkit-scrollbar-thumb:hover {
-  background: #555;
+  background: rgba(85, 85, 85, 0.8);
 }
 
-.el-button, .el-button.is-round {
-    margin-left: 15px ;
+/* 响应式调整 */
+@media (max-width: 1200px) {
+  .district-item {
+    width: 100%;
+    max-width: 100%;
+  }
+  
+  .background-container {
+    width: 250px;
+  }
+  
+  .content-area {
+    margin-left: 250px;
+  }
 }
 
+@media (max-width: 768px) {
+  .background-container {
+    position: relative;
+    width: 100%;
+    height: auto;
+    padding-bottom: 30px;
+  }
+  
+  .content-area {
+    margin-left: 0;
+  }
+  
+  .city-buttons-container {
+    flex-direction: row;
+    flex-wrap: wrap;
+    justify-content: center;
+    padding: 30px 20px 20px;
+    gap: 15px;
+  }
+  
+  .city-button {
+    width: 45%;
+    height: 60px;
+    font-size: 18px;
+  }
+  
+  .title {
+    margin-bottom: 30px;
+  }
+  
+  .district-item {
+    min-width: 100%;
+  }
+}
 </style>

+ 92 - 78
src/views/login/loginView.vue

@@ -6,14 +6,7 @@
     <!-- 登录/注册表单部分 -->
     <div class="auth-form-container">
       <!-- 登录表单 -->
-      <el-form
-        v-if="isLogin"
-        ref="formRef"
-        :model="form"
-        :rules="rules"
-        label-width="100px"
-        class="login-form"
-      >
+      <el-form v-if="isLogin" ref="formRef" :model="form" :rules="rules" label-width="116px" class="login-form">
         <div class="form-header">
           <!-- 根据用户类型显示不同的标题 -->
           <h2 class="form-title">
@@ -25,20 +18,26 @@
           </h2>
           <!-- 切换用户类型的按钮 -->
           <el-button class="user-type-toggle" @click="toggleUserType" link>
-            <el-icon><User /></el-icon>
+            <el-icon>
+              <User />
+            </el-icon>
             <span>{{ currentUserTypeName }}</span>
           </el-button>
         </div>
 
         <!-- 用户名输入框 -->
-        <el-form-item :label="$t('login.username')" prop="name">
-          <el-input v-model="form.name"></el-input>
-        </el-form-item>
+        <div class="input-frame">
+          <el-form-item label="账号:" prop="name">
+            <el-input v-model="form.name"></el-input>
+          </el-form-item>
+        </div>
 
         <!-- 密码输入框 -->
-        <el-form-item :label="$t('login.password')" prop="password">
-          <el-input type="password" v-model="form.password"></el-input>
-        </el-form-item>
+        <div class="input-frame">
+          <el-form-item label="密码:" prop="password">
+            <el-input type="password" v-model="form.password"></el-input>
+          </el-form-item>
+        </div>
 
         <!-- 语言切换按钮 -->
         <div class="language-toggle-wrapper">
@@ -50,12 +49,7 @@
         <!-- 提交登录按钮和注册链接 -->
         <el-form-item>
           <div class="button-group">
-            <el-button
-              type="primary"
-              @click="onSubmit"
-              :loading="loading"
-              class="login-button"
-            >
+            <el-button type="primary" @click="onSubmit" :loading="loading" class="login-button">
               {{ $t("login.loginButton") }}
             </el-button>
           </div>
@@ -68,44 +62,40 @@
       </el-form>
 
       <!-- 注册表单 -->
-      <el-form
-        v-else
-        ref="registerFormRef"
-        :model="registerForm"
-        :rules="registerRules"
-        label-width="100px"
-        class="login-form"
-      >
+      <el-form v-else ref="registerFormRef" :model="registerForm" :rules="registerRules" label-width="116px"
+        class="login-form">
         <div class="form-header">
           <!-- 注册表单标题 -->
           <h2 class="form-title">{{ $t("register.title") }}</h2>
           <!-- 切换用户类型的按钮 -->
           <el-button class="user-type-toggle" @click="toggleUserType" link>
-            <el-icon><User /></el-icon>
+            <el-icon>
+              <User />
+            </el-icon>
             <span>{{ currentUserTypeName }}</span>
           </el-button>
         </div>
 
         <!-- 用户名输入框 -->
-        <el-form-item :label="$t('register.username')" prop="name">
-          <el-input v-model="registerForm.name"></el-input>
-        </el-form-item>
+        <div class="input-frame">
+          <el-form-item label="账号:" prop="name">
+            <el-input v-model="registerForm.name"></el-input>
+          </el-form-item>
+        </div>
 
         <!-- 密码输入框 -->
-        <el-form-item :label="$t('register.password')" prop="password">
-          <el-input type="password" v-model="registerForm.password"></el-input>
-        </el-form-item>
+        <div class="input-frame">
+          <el-form-item label="密码:" prop="password">
+            <el-input type="password" v-model="registerForm.password"></el-input>
+          </el-form-item>
+        </div>
 
         <!-- 确认密码输入框 -->
-        <el-form-item
-          :label="$t('register.confirmPassword')"
-          prop="confirmPassword"
-        >
-          <el-input
-            type="password"
-            v-model="registerForm.confirmPassword"
-          ></el-input>
-        </el-form-item>
+        <div class="input-frame">
+          <el-form-item label="确认密码:" prop="confirmPassword">
+            <el-input type="password" v-model="registerForm.confirmPassword"></el-input>
+          </el-form-item>
+        </div>
 
         <!-- 语言切换按钮 -->
         <div class="language-toggle-wrapper">
@@ -116,17 +106,12 @@
         <!-- 提交注册按钮和返回登录链接 -->
         <el-form-item>
           <div class="button-group">
-            <el-button
-              type="primary"
-              @click="onRegister"
-              :loading="loading"
-              class="login-button"
-            >
+            <el-button type="primary" @click="onRegister" :loading="loading" class="login-button">
               {{ $t("register.registerButton") }}
             </el-button>
           </div>
-          <div class="button-group">
-            <span type="primary" @click="toggleForm" :loading="loading" class="register-button">
+          <div class="button-group register-link-container">
+            <span @click="toggleForm" class="register-button">
               {{ $t("register.backToLoginButton") }}
             </span>
           </div>
@@ -368,23 +353,29 @@ const registerRules = reactive({
 }
 
 .auth-left {
-  width: 45%;
+  width: 35%;
   background: url("@/assets/login-bg.png") no-repeat center center;
   background-size: cover;
 }
 
+/* 调整表单容器大小 */
 .auth-form-container {
-  width: 50%;
-  padding: 0 40px 0 90px;
+  width: 55%;
+  padding: 0 40px 0 60px;
   display: flex;
   justify-content: center;
-  align-items: flex-start;
+  align-items: center;
+  flex-direction: column;
 }
 
+/* 调整表单整体大小 */
 .login-form {
   width: 100%;
-  padding: 40px 0 60px 60px;
-  margin-top: 70px;
+  max-width: 700px;
+  padding: 40px 30px;
+  margin-top: 50px;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 15px;
 }
 
 /* 表单头部 */
@@ -392,8 +383,8 @@ const registerRules = reactive({
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 50px;
-  margin-top: 40px;
+  margin-bottom: 40px;
+  margin-top: 20px;
 }
 
 .form-title {
@@ -420,8 +411,7 @@ const registerRules = reactive({
 /* 语言切换按钮 */
 .language-toggle-wrapper {
   text-align: right;
-  margin: 5px 0 1px;
-  transform: translateX(-210px);
+  margin: 5px 0 10px;
 }
 
 /* 表单项样式 */
@@ -430,39 +420,61 @@ const registerRules = reactive({
   display: block;
   text-align: left;
   font-size: 24px;
-  padding-bottom: 4px;
+  padding-bottom: 8px;
+  color: #7E7878;
 }
 
 :deep(.el-form-item) {
   display: flex;
   flex-direction: column;
-  margin-bottom: 25px;
+  margin-bottom: 0;
+}
+
+/* 输入框样式 - 改为长方形 */
+:deep(.el-input) {
+  width: 100%;
+}
+
+:deep(.el-input .el-input__inner) {
+  height: 50px;
+  font-size: 20px;
+  border-radius: 0; /* 将圆角改为0,变成直角 */
+  border: 1px solid #dcdfe6;
+  background-color: #ffffff;
+  padding: 0 15px;
 }
 
 /* 登录按钮 */
 .login-button {
   background: linear-gradient(to right, #8df9f0, #26b046);
-  width: 393px;
+  width: 100%;
+  max-width: 400px;
   height: 56px;
   color: white;
   border: none;
   border-radius: 20px;
   font-size: 24px;
   cursor: pointer;
-  margin-top: -30px; /* 向上移动20px */
+  margin-top: 10px;
 }
 
 .login-button:hover {
   opacity: 0.9;
 }
 
+/* 注册按钮容器 */
+.register-link-container {
+  margin-top: 20px;
+}
+
 .register-button {
-  text-align: right;
-  margin: 90px 0 1px;
+  display: block;
+  text-align: center;
   color: #478bf0;
-  font-size: 14px;
+  font-size: 18px;
   cursor: pointer;
-  transform: translateX(-240px);
+  padding: 10px 0;
+  width: 100%;
 }
 
 .register-button:hover {
@@ -474,11 +486,12 @@ const registerRules = reactive({
 .button-group {
   display: flex;
   justify-content: center;
+  width: 100%;
 }
 
 .text-toggle {
   color: #478bf0;
-  font-size: 14px;
+  font-size: 16px;
   cursor: pointer;
 }
 
@@ -489,17 +502,18 @@ const registerRules = reactive({
 
 .language-toggle-wrapper {
   text-align: right;
-  margin: 10px 0 40px;
+  margin: 15px 0 20px;
 }
 
 .text-link-wrapper {
   text-align: center;
-  margin-left: -295px;
-  margin-top: 120px;
+  margin-top: 20px;
 }
 
-:deep(.el-input) {
-  max-width: 560px;
+.input-frame {
+  background-color: #fff;
   width: 100%;
+  padding: 15px 10px;
+  margin-bottom: 20px;
 }
-</style>
+</style>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 46 - 0
vitest.config.ts.timestamp-1753233310015-7835fe80b1bda.mjs


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 46 - 0
vitest.config.ts.timestamp-1753239092747-1a7d0d2229938.mjs


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác