瀏覽代碼

Merge branch 'ding'

yangtaodemon 1 月之前
父節點
當前提交
abd661a336

二進制
public/images/天峨县.jpg


二進制
public/images/曲江区.jpg


二進制
public/农业化肥采集.png


二進制
public/农业投入品使用情况.png


二進制
public/农业投入品样品采集.png


+ 3 - 84
src/components/layout/AppLayout.vue

@@ -121,91 +121,10 @@ const showGlobalMessage = ref(false);
 const globalMessage = ref("");
 const messageType = ref("success"); // 消息类型: success/error
 
-// 特殊背景路由映射
-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",
-  "/totalInputFlux": "background.jpg",
-  "/totalInputFluxDesc": "background.jpg",
-  "/totalOutputFlux": "background.jpg",
-  "/totalOutputFluxDesc": "background.jpg",
-  "/netFlux": "background.jpg",
-  "/currentYearConcentration": "background.jpg",
-  "/TotalCadmiumPrediction": "background.jpg",
-  "/EffectiveCadmiumPrediction": "background.jpg",
-  "/CropCadmiumPrediction": "background.jpg",
-  "/cropRiskAssessment": "background.jpg",
-  "/farmlandQualityAssessment": "background.jpg",
-  "/Calculation": "background.jpg",
-  "/SoilAcidReductionIterativeEvolution": "background.jpg",
-  "/AcidNeutralizationModel": "background.jpg",
-  "/SoilAcidificationIterativeEvolution": "background.jpg",
-  "/TraditionalFarmingRisk": "background.jpg",
-  "/HeavyMetalCadmiumControl": "background.jpg",
-  "/SoilAcidificationControl": "background.jpg",
-  "/DetectionStatistics": "background.jpg",
-  "/FarmlandPollutionStatistics": "background.jpg",
-  "/LandClutivatesStatistics": "background.jpg",
-  "/soilAcidReductionData": "background.jpg",
-  "/soilAcidificationData": "background.jpg",
-  "/crossSectionSampleData": "background.jpg",
-  "/irrigationWaterInputFluxData": "background.jpg",
-  "/heavyMetalEnterpriseData": "background.jpg",
-  "/atmosphericSampleData": "background.jpg",
-  "/CadmiumPredictionModel": "background.jpg",
-  "/ModelSelection": "background.jpg",
-  "/thres": "background.jpg",
-  "/ModelTrain": "background.jpg",
-  "/RiceRiskModel": "background.jpg",
-  "/WheatRiskModel": "background.jpg",
-  "/VegetableRiskModel": "background.jpg",
-  "/UserManagement": "background.jpg",
-  "/UserRegistration": "background.jpg",
-  "/SoilacidificationStatistics": "background.jpg",
-};
-
-// 当前是否特殊背景
-const isSpecialBg = computed(() =>
-  Object.keys(bgRouteMap).includes(route.path)
-);
+currentBgImage.value = `url(${new URL('../../assets/bg/background.jpg', import.meta.url).href})`;
 
-function getBgImageUrl(): string {
-  const bgFile = bgRouteMap[route.path];
-  if (bgFile) {
-    try {
-      const url = `url(${new URL(`../../assets/bg/background.jpg`, import.meta.url).href})`;
-      return url;
-    } catch (error) {
-      console.error("加载背景图失败:", error);
-      return "";
-    }
-  }
-  return "";
-}
-
-watch(
-  () => route.path,
-  () => {
-    currentBgImage.value = getBgImageUrl();
-  },
-  { immediate: true }
-);
+// 是否特殊背景(始终返回true → 所有页面应用特殊背景样式)
+const isSpecialBg = computed(() => true);
 
 const backgroundStyle = computed(() => ({
   backgroundImage: currentBgImage.value,

+ 366 - 336
src/components/layout/menuItems.ts

@@ -26,340 +26,370 @@ export interface MenuItem {
   children?: MenuItem[];
 }
 
-// 按 tab 分组的菜单映射
-export const tabMenuMap: Record<string, MenuItem[]> = {
-  shuJuKanBan: [
-    {
-      index: "/shuJuKanBan",
-      label: "shuJuKanBan.Title",
-      icon: Monitor,
-    },
-  ],
+export const menuItems: MenuItem[] = [
+  {
+    index: '/shuJuKanBan',
+    label: 'shuJuKanBan.Title',//<!--i18n:shuJuKanBan.Title-->数据看板
+    icon: Monitor,
+    tab: 'shuJuKanBan'
+  },
+  {
+    index: '/SoilPro',
+    label: 'SoilPro.Title',//<!--i18n:SoilPro.Title-->软件简介
+    icon: InfoFilled,
+    tab: 'introduction'
+  },
+  {
+    index: '/Overview',
+    label: 'Overview.Title',//<!--i18n:Overview.Title-->项目简介
+    icon: Collection,
+    tab: 'introduction'
+  },
+  {
+    index: '/ResearchFindings',
+    label: 'ResearchFindings.Title',//<!--i18n:ResearchFindings.Title-->研究成果
+    icon: Histogram,
+    tab: 'introduction'
+  },
+  {
+    index: '/Unit',
+    label: 'Unit.Title',//<!--i18n:Unit.Title-->团队信息
+    icon: HelpFilled,
+    tab: 'introduction'
+  },
+   {
+    index: '/CropCadmiumPrediction',
+    label: 'CropCadmiumPrediction.Title',//<!--i18n:CropCadmiumPrediction.Title-->土壤镉作物态含量预测
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
+  {
+    index: '/EffectiveCadmiumPrediction',
+    label: 'EffectiveCadmiumPrediction.Title',//<!--i18n:EffectiveCadmiumPrediction.Title-->土壤镉有效态含量预测
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
+  {
+    index: '/FutureCadmiumPrediction',
+    label: 'FutureCadmiumPrediction.Title',//<!--i18n:EffectiveCadmiumPrediction.Title-->土壤镉有效态含量预测
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
+  {
+    index: '/netFlux',
+    label: 'netFlux.Title',//<!--i18n:netFlux.Title-->净通量
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
+  {
+    index: '/currentYearConcentration',
+    label: 'currentYearConcentration.Title',//<!--i18n:currentYearConcentration.Title-->当年浓度
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
+  {
+    index: 'irrigationWater',
+    label: 'irrigationwater.Title',//灌溉水
+    icon: Watermelon,
+    tab: 'HmOutFlux',
+    children: [
+      {
+        index: '/samplingMethodDevice1',
+        label: 'irrigationwater.irrigationwaterMethodsTitle',
+        icon: Sunny,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/irriSampleData',
+        label: 'irrigationwater.pointTitle',
+        icon: Coin,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/csSampleData',
+        label: 'irrigationwater.crosssectionTitle',
+        icon: Cloudy,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/irriInputFlux',
+        label: 'irrigationwater.InputfluxTitle',
+        icon: Cloudy,
+        tab: 'HmOutFlux'
+      }
+    ]
+  },
+  {
+    index: 'inputFlux',
+    label: 'agriInput.Title',//农产品投入
+    icon: Watermelon,
+    tab: 'HmOutFlux',
+    children: [
+      {
+        index: '/farmInputSamplingDesc',
+        label: 'agriInput.farmInputSamplingDescTitle',//采样说明
+        icon: Sunny,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/prodInputFlux',
+        label: 'agriInput.prodInputFluxTitle',//农产品输入通量
+        icon: Coin,
+        tab: 'HmOutFlux'
+      },
+    ]
+  },
+  {
+    index: 'atmosDeposition',
+    label: 'atmosDeposition.Title',//大气干湿沉降
+    icon: Watermelon,
+    tab: 'HmOutFlux',
+    children: [
+      {
+        index: '/AtmosDepositionSamplingDesc',
+        label: 'atmosDeposition.AtmosDepositionSamplingDescTitle',//采样说明
+        icon: Sunny,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/heavyMetalEnterprise',
+        label: 'atmosDeposition.heavyMetalEnterpriseTitle',//涉重企业
+        icon: Coin,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/airSampleData',
+        label: 'atmosDeposition.airSampleDataTitle',//大气采样数据
+        icon: Sunny,
+        tab: 'HmOutFlux'
+      },
+      {
+        index: '/airInputFlux',
+        label: 'atmosDeposition.airInputFluxTitle',//大气输入通量
+        icon: Coin,
+        tab: 'HmOutFlux'
+      },
+    ]
+  },
+  {
+    index: 'totalInputFlux',
+    label: '输入总通量',
+    icon: WindPower,
+    tab: 'HmOutFlux',
+    children: [
+      {
+        index: '/totalInputFluxDesc',
+        label: '输入总通量说明',
+        icon: Watermelon,
+        tab: 'HmOutFlux',
+      },
+      {
+        index: '/totalInputFlux',
+        label: '输入总通量结果',
+        icon: List,
+        tab: 'HmOutFlux',
+      },
+    ]
+  },
+  {
+    index: 'grainRemoval',
+    label: 'grainRemoval.Title',//<!--i18n:grainRemoval.Title-->籽粒移除
+    icon: WindPower,
+    tab: 'hmInFlux',
+    children: [
+      {
+        index: '/samplingDesc1',
+        label: 'grainRemoval.samplingDesc1',//<!--i18n:grainRemoval.samplingDesc1-->采样说明
+        icon: Watermelon,
+        tab: 'hmInFlux'
+      },
+      {
+        index: '/grainRemovalInputFlux',
+        label: 'grainRemoval.grainRemovalInputFlux',//<!--i18n:grainRemoval.grainRemovalInputFlux-->籽粒移除输出通量
+        icon: List,
+        tab: 'hmInFlux'
+      }
+    ]
+  },
+  {
+    index: 'strawRemoval',
+    label: 'strawRemoval.Title',//<!--i18n:strawRemoval.Title-->秸秆移除
+    icon: WindPower,
+    tab: 'hmInFlux',
+    children: [
+      {
+        index: '/samplingDesc2',
+        label: 'strawRemoval.samplingDesc2',//<!--i18n:strawRemoval.samplingDesc2-->采样说明
+        icon: Watermelon,
+        tab: 'hmInFlux'
+      },
+      {
+        index: '/strawRemovalInputFlux',
+        label: 'strawRemoval.strawRemovalInputFlux',//<!--i18n:strawRemoval.strawRemovalInputFlux-->秸秆移除输出通量
+        icon: List,
+        tab: 'hmInFlux'
+      }
+    ]
+  },
+  {
+    index: 'subsurfaceLeakage',
+    label: 'subsurfaceLeakage.Title',//<!--i18n:subsurfaceLeakage.Title-->地下渗漏
+    icon: WindPower,
+    tab: 'hmInFlux',
+    children: [
+      {
+        index: '/samplingDesc3',
+        label: 'subsurfaceLeakage.samplingDesc3',//<!--i18n:subsurfaceLeakage.samplingDesc3-->采样说明
+        icon: Watermelon,
+        tab: 'hmInFlux'
+      },
+      {
+        index: '/subsurfaceLeakageInputFlux',
+        label: 'subsurfaceLeakage.subsurfaceLeakageInputFlux',//<!--i18n:subsurfaceLeakage.subsurfaceLeakageInputFlux-->地下渗漏输入通量
+        icon: List,
+        tab: 'hmInFlux'
+      }
+    ]
+  },
+  {
+    index: 'surfaceRunoff',
+    label: 'surfaceRunoff.Title',//<!--i18n:surfaceRunoff.Title-->地表径流
+    icon: WindPower,
+    tab: 'hmInFlux',
+    children: [
+      {
+        index: '/samplingDesc4',
+        label: 'surfaceRunoff.samplingDesc4',//<!--i18n:surfaceRunoff.samplingDesc4-->采样说明
+        icon: Watermelon,
+        tab: 'hmInFlux'
+      },
+      {
+        index: '/surfaceRunoffInputFlux',
+        label: 'surfaceRunoff.surfaceRunoffInputFlux',//<!--i18n:surfaceRunoff.surfaceRunoffInputFlux-->地表径流输入通量
+        icon: List,
+        tab: 'hmInFlux'
+      }
+    ]
+  },
+  {
+    index: 'totalOutputFlux',
+    label: '输出总通量',
+    icon: WindPower,
+    tab: 'hmInFlux',
+    children: [
+      {
+        index: '/totalOutputFluxDesc',
+        label: '输出总通量说明',
+        icon: Watermelon,
+        tab: 'hmInFlux',
+      },
+      {
+        index: '/totalOutputFlux',
+        label: '输出总通量结果',
+        icon: List,
+        tab: 'hmInFlux',
+      },
+    ]
+  },
+  {
+    index: '/mapView',
+    label: 'mapView.Title',//<!--i18n:mapView.Title-->地图展示
+    icon: Location,
+    tab: 'mapView'
+  },
+  // {
+  //   index: '/cropRiskAssessment',
+  //   label: 'cropRiskAssessment.Title',//<!--i18n:cropRiskAssessment.Title-->水稻镉污染风险
+  //   icon: Compass,
+  //   tab: 'cropRiskAssessment'
+  // },
+  {
+    index: '/farmlandQualityAssessment',
+    label: 'farmlandQualityAssessment.Title',//<!--i18n:farmlandQualityAssessment.Title-->韶关
+    icon: DataLine,
+    tab: 'farmlandQualityAssessment'
+  },
+  {
+    index: '/acidModel',
+    label: 'acidModel.Title',//<!--i18n:acidModel.Title-->土壤反酸
+    icon: MagicStick,
+    tab: 'soilAcidificationPrediction',
+    children: [
+      {
+        index: '/Calculation',
+        label: 'acidModel.CalculationTitle',//<!--i18n:acidModel.CalculationTitle-->土壤反酸预测
+        icon: Sunny,
+        tab: 'heavyMetalFluxCalculation'
+      },
+      {
+        index: '/SoilAcidReductionIterativeEvolution',
+        label: 'acidModel.SoilAcidReductionIterativeEvolutionTitle',//<!--i18n:acidModel.SoilAcidReductionIterativeEvolutionTitle-->反酸模型迭代可视化
+        icon: Coin,
+        tab: 'heavyMetalFluxCalculation'
+      }
+    ]
+  },
+  {
+    index: '/neutralizationModel',
+    label: 'neutralizationModel.Title',//<!--i18n:neutralizationModel.Title-->土壤降酸
+    icon: MagicStick,
+    tab: 'soilAcidificationPrediction',
+    children: [
+      {
+        index: '/AcidNeutralizationModel',
+        label: 'neutralizationModel.AcidNeutralizationModelTitle',//<!--i18n:neutralizationModel.AcidNeutralizationModelTitle-->土壤降酸预测
+        icon: Sunny,
+        tab: 'heavyMetalFluxCalculation'
+      },
+      {
+        index: '/SoilAcidificationIterativeEvolution',
+        label: 'neutralizationModel.SoilAcidificationIterativeEvolutionTitle',//<!--i18n:neutralizationModel.SoilAcidificationIterativeEvolutionTitle-->土壤降酸可视化
+        icon: Coin,
+        tab: 'heavyMetalFluxCalculation'
+      }
+    ]
+  },
+  // {
+  //   index: '/TraditionalFarmingRisk',
+  //   label: 'TraditionalFarmingRisk.Title',//<!--i18n:TraditionalFarmingRisk.Title-->传统耕种习惯风险趋势
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
+  // {
+  //   index: '/HeavyMetalCadmiumControl',
+  //   label: 'HeavyMetalCadmiumControl.Title',//<!--i18n:HeavyMetalCadmiumControl.Title-->重金属镉污染治理
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
+  // {
+  //   index: '/SoilAcidificationControl',
+  //   label: 'SoilAcidificationControl.Title',//<!--i18n:SoilAcidificationControl.Title-->土壤酸化治理
+  //   icon: MenuIcon,
+  //   tab: 'scenarioSimulation'
+  // },
+  {
+    index: '/DetectionStatistics',
+    label: 'DetectionStatistics.Title',//<!--i18n:DetectionStatistics.Title-->检测信息统计
+    icon: List,
+    tab: 'dataStatistics'
+  },
+  {
+    index: '/FarmlandPollutionStatistics',
+    label: 'FarmlandPollutionStatistics.Title',//<!--i18n:FarmlandPollutionStatistics.Title-->土壤镉含量统计
+    icon: List,
+    tab: 'dataStatistics'
+  },
+  {
+    index: '/LandClutivatesStatistics',
+    label: 'LandClutivatesStatistics.Title',//<!--i18n:LandClutivatesStatistics.Title-->作物风险评估统计
+    icon: List,
+    tab: 'dataStatistics'
+  },
+  {
+    index: '/SoilacidificationStatistics',
+    label: 'SoilacidificationStatistics.Title',//<!--i18n:SoilacidificationStatistics.Title-->酸化预测数据统计
+    icon: List,
+    tab: 'dataStatistics'
+  }
+].filter(({ tab: menuTab }) => !["shuJuKanBan", "mapView", "introduction"].includes(menuTab));
 
-  introduction: [
-    {
-      index: "/SoilPro",
-      label: "SoilPro.Title",
-      icon: InfoFilled,
-    },
-    {
-      index: "/Overview",
-      label: "Overview.Title",
-      icon: Collection,
-    },
-    {
-      index: "/ResearchFindings",
-      label: "ResearchFindings.Title",
-      icon: Histogram,
-    },
-    {
-      index: "/Unit",
-      label: "Unit.Title",
-      icon: HelpFilled,
-    },
-  ],
-
-  mapView: [
-    {
-      index: "/mapView",
-      label: "mapView.Title",
-      icon: Location,
-    },
-  ],
-
-  HmOutFlux: [
-    {
-      index: "irrigationWater",
-      label: "irrigationwater.Title",
-      icon: Watermelon,
-      children: [
-        {
-          index: "/samplingMethodDevice1",
-          label: "irrigationwater.irrigationwaterMethodsTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/irriSampleData",
-          label: "irrigationwater.pointTitle",
-          icon: Coin,
-        },
-        {
-          index: "/csSampleData",
-          label: "irrigationwater.crosssectionTitle",
-          icon: Cloudy,
-        },
-        {
-          index: "/irriInputFlux",
-          label: "irrigationwater.InputfluxTitle",
-          icon: Cloudy,
-        },
-      ],
-    },
-    {
-      index: "inputFlux",
-      label: "agriInput.Title",
-      icon: Watermelon,
-      children: [
-        {
-          index: "/farmInputSamplingDesc",
-          label: "agriInput.farmInputSamplingDescTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/prodInputFlux",
-          label: "agriInput.prodInputFluxTitle",
-          icon: Coin,
-        },
-      ],
-    },
-    {
-      index: "atmosDeposition",
-      label: "atmosDeposition.Title",
-      icon: Watermelon,
-      children: [
-        {
-          index: "/AtmosDepositionSamplingDesc",
-          label: "atmosDeposition.AtmosDepositionSamplingDescTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/heavyMetalEnterprise",
-          label: "atmosDeposition.heavyMetalEnterpriseTitle",
-          icon: Coin,
-        },
-        {
-          index: "/airSampleData",
-          label: "atmosDeposition.airSampleDataTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/airInputFlux",
-          label: "atmosDeposition.airInputFluxTitle",
-          icon: Coin,
-        },
-      ],
-    },
-    {
-      index: "/totalInputFlux",
-      label: "totalInputFlux.Title",
-      icon: Watermelon,
-      children: [
-        {
-          index: "/totalInputFluxDesc",
-          label: "输入总通量说明",
-          icon: Watermelon,
-        },
-        {
-          index: "/totalInputFlux",
-          label: "输入总通量结果",
-          icon: List,
-        },
-      ],
-    },
-  ],
-
-  hmInFlux: [
-    {
-      index: "grainRemoval",
-      label: "grainRemoval.Title",
-      icon: WindPower,
-      children: [
-        {
-          index: "/samplingDesc1",
-          label: "grainRemoval.samplingDesc1",
-          icon: Watermelon,
-        },
-        {
-          index: "/grainRemovalInputFlux",
-          label: "grainRemoval.grainRemovalInputFlux",
-          icon: List,
-        },
-      ],
-    },
-    {
-      index: "strawRemoval",
-      label: "strawRemoval.Title",
-      icon: WindPower,
-      children: [
-        {
-          index: "/samplingDesc2",
-          label: "strawRemoval.samplingDesc2",
-          icon: Watermelon,
-        },
-        {
-          index: "/strawRemovalInputFlux",
-          label: "strawRemoval.strawRemovalInputFlux",
-          icon: List,
-        },
-      ],
-    },
-    {
-      index: "subsurfaceLeakage",
-      label: "subsurfaceLeakage.Title",
-      icon: WindPower,
-      children: [
-        {
-          index: "/samplingDesc3",
-          label: "subsurfaceLeakage.samplingDesc3",
-          icon: Watermelon,
-        },
-        {
-          index: "/subsurfaceLeakageInputFlux",
-          label: "subsurfaceLeakage.subsurfaceLeakageInputFlux",
-          icon: List,
-        },
-      ],
-    },
-    {
-      index: "surfaceRunoff",
-      label: "surfaceRunoff.Title",
-      icon: WindPower,
-      children: [
-        {
-          index: "/samplingDesc4",
-          label: "surfaceRunoff.samplingDesc4",
-          icon: Watermelon,
-        },
-        {
-          index: "/surfaceRunoffInputFlux",
-          label: "surfaceRunoff.surfaceRunoffInputFlux",
-          icon: List,
-        },
-      ],
-    },
-    {
-      index: "totalOutputFlux",
-      label: "totalOutputFlux.Title",
-      icon: WindPower,
-      children: [
-        {
-          index: "/totalOutputFluxDesc",
-          label: "输入总通量说明",
-          icon: Watermelon,
-        },
-        {
-          index: "/totalOutputFlux",
-          label: "输出总通量结果",
-          icon: List,
-        },
-      ],
-    },
-  ],
-
-  cadmiumPrediction: [
-    {
-      index: "/netFlux",
-      label: "netFlux.Title",
-      icon: PieChart,
-    },
-    {
-      index: "/currentYearConcentration",
-      label: "currentYearConcentration.Title",
-      icon: PieChart,
-    },
-    {
-      index: "/EffectiveCadmiumPrediction",
-      label: "EffectiveCadmiumPrediction.Title",
-      icon: PieChart,
-    },
-    {
-      index: "/CropCadmiumPrediction",
-      label: "CropCadmiumPrediction.Title",
-      icon: PieChart,
-    },
-  ],
-
-  cropRiskAssessment: [
-    {
-      index: "/cropRiskAssessment",
-      label: "cropRiskAssessment.Title",
-      icon: Compass,
-    },
-  ],
-
-  farmlandQualityAssessment: [
-    {
-      index: "/farmlandQualityAssessment",
-      label: "farmlandQualityAssessment.Title",
-      icon: DataLine,
-    },
-  ],
-
-  soilAcidificationPrediction: [
-    {
-      index: "acidModel",
-      label: "acidModel.Title",
-      icon: MagicStick,
-      children: [
-        {
-          index: "/Calculation",
-          label: "acidModel.CalculationTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/SoilAcidReductionIterativeEvolution",
-          label: "acidModel.SoilAcidReductionIterativeEvolutionTitle",
-          icon: Coin,
-        },
-      ],
-    },
-    {
-      index: "neutralizationModel",
-      label: "neutralizationModel.Title",
-      icon: MagicStick,
-      children: [
-        {
-          index: "/AcidNeutralizationModel",
-          label: "neutralizationModel.AcidNeutralizationModelTitle",
-          icon: Sunny,
-        },
-        {
-          index: "/SoilAcidificationIterativeEvolution",
-          label: "neutralizationModel.SoilAcidificationIterativeEvolutionTitle",
-          icon: Coin,
-        },
-      ],
-    },
-  ],
-
-  scenarioSimulation: [
-    {
-      index: "/TraditionalFarmingRisk",
-      label: "TraditionalFarmingRisk.Title",
-      icon: MenuIcon,
-    },
-    {
-      index: "/HeavyMetalCadmiumControl",
-      label: "HeavyMetalCadmiumControl.Title",
-      icon: MenuIcon,
-    },
-    {
-      index: "/SoilAcidificationControl",
-      label: "SoilAcidificationControl.Title",
-      icon: MenuIcon,
-    },
-  ],
-
-  dataStatistics: [
-    {
-      index: "/DetectionStatistics",
-      label: "DetectionStatistics.Title",
-      icon: List,
-    },
-    {
-      index: "/FarmlandPollutionStatistics",
-      label: "FarmlandPollutionStatistics.Title",
-      icon: List,
-    },
-    {
-      index: "/LandClutivatesStatistics",
-      label: "LandClutivatesStatistics.Title", //<!--i18n:LandClutivatesStatistics.Title-->作物风险评估统计
-      icon: List,
-    },
-    {
-      index: "/SoilacidificationStatistics",
-      label: "SoilacidificationStatistics.Title", //<!--i18n:SoilacidificationStatistics.Title-->酸化预测数据统计
-      icon: List,
-    },
-  ],
-};

+ 4 - 1
src/locales/zh.json

@@ -151,7 +151,10 @@
     "Title": "土壤镉有效态含量预测"
   },
   "CropCadmiumPrediction": {
-    "Title": "土壤镉作物态含量预测"
+    "Title": "水稻镉含量预测"
+  },
+  "FutureCadmiumPrediction": {
+    "Title": "土壤镉未来含量预测"
   },
   "cropRiskAssessment": {
     "Title": "水稻镉污染风险"

+ 7 - 0
src/router/index.ts

@@ -339,6 +339,13 @@ const routes = [
         component: () => import("@/views/User/cadmiumPrediction/netFlux.vue"), // 修复路径
         meta: { title: "净通量" },
       },
+      {
+        path: "FutureCadmiumPrediction",
+        name: "FutureCadmiumPrediction",
+        component: () =>
+          import("@/views/User/cadmiumPrediction/FutureCadmiumPrediction.vue"), // 修复路径
+        meta: { title: "未来浓度预测" },
+      },
       {
         path: "currentYearConcentration",
         name: "currentYearConcentration",

+ 327 - 142
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -1,8 +1,7 @@
 <template>
   <div class="container">
     <!-- 顶部操作栏 -->
-    <div class="toolbar">
-      <!-- 文件上传区域 -->
+    <!-- <div class="toolbar">
       <div class="upload-section">
         <input type="file" ref="fileInput" accept=".csv" @change="handleFileUpload" style="display: none">
         <el-button class="custom-button" @click="triggerFileUpload">
@@ -19,7 +18,6 @@
         <el-icon class="upload-icon"><Document /></el-icon>  
         上传并计算
         </el-button>
-        <!-- 添加从数据库计算按钮 -->
         <el-button 
           class="custom-button" 
           :loading="isCalculatingFromDB" 
@@ -28,7 +26,7 @@
         <el-icon class="upload-icon"><Box /></el-icon>  
         从数据库计算
         </el-button>
-      </div>
+      </div> -->
       <!-- 操作按钮 -->
       <!-- <div class="action-buttons">
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
@@ -41,23 +39,28 @@
           <el-icon class="upload-icon"><Download /></el-icon>
           导出数据</el-button>
       </div> -->
-    </div>
+    <!-- </div> -->
 
     <!-- 主体内容区 -->
     <div class="content-area">
       <!-- 地图区域 - 单独一行 -->
       <div class="map-section">
         <h3>水稻Cd预测地图</h3>
-        <div v-if="loadingMap" class="loading-container">
-          <el-icon class="loading-icon"><Loading /></el-icon>
-          <span>地图加载中...</span>
-        </div>
-        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="作物态Cd预测地图" class="map-image">
-        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
-          <el-icon><Picture /></el-icon>
-          <p>暂无地图数据</p>
+         <!-- 颜色图例 -->
+        <div class="legend">
+          <div class="legend-item">
+            <div class="color-box" style="background-color: #15b392;"></div>
+            <span>安全区间 (0.0-0.2 mg/kg)</span>
+          </div>
+          <div class="legend-item">
+            <div class="color-box" style="background-color: #ffea56;"></div>
+            <span>预警区间 (0.2-0.3 mg/kg)</span>
+          </div>
+          <div class="legend-item">
+            <div class="color-box" style="background-color: #FF0000;"></div>
+            <span>超标区间 (≥0.3 mg/kg)</span>
+          </div>
         </div>
-
         <div v-if="loadingTownshipMap" class="loading-container">
           <el-icon class="loading-icon"><Loading /></el-icon>
           <span>乡镇地图加载中...</span>
@@ -71,37 +74,51 @@
           class="township-map-container"
         ></div>
         </div>
-        
-        <!-- 颜色图例 -->
-        <div class="legend">
-          <div class="legend-item">
-            <div class="color-box" style="background-color: #00FF00;"></div>
-            <span>安全区间 (0.0-0.2 mg/kg)</span>
-          </div>
-          <div class="legend-item">
-            <div class="color-box" style="background-color: #FFFF00;"></div>
-            <span>预警区间 (0.2-0.3 mg/kg)</span>
-          </div>
-          <div class="legend-item">
-            <div class="color-box" style="background-color: #FF0000;"></div>
-            <span>超标区间 (≥0.3 mg/kg)</span>
-          </div>
-          <div class="legend-item">
-            <div class="color-box" style="background-color: #CCCCCC;"></div>
-            <span>无数据</span>
-          </div>
+         <!-- 新增:要素详细信息栏 -->
+        <div v-if="currentFeatureInfo" class="feature-info-panel">
+          <h4 style="margin-top: 0; color: #0066CC; border-bottom: 1px solid #eee; padding-bottom: 10px;">
+            地块详细信息
+          </h4>
+          <ul style="list-style: none; padding: 0; margin: 0;">
+            <li style="margin-bottom: 8px; font-size: 14px;">
+              <strong>水稻镉含量:</strong>{{ currentFeatureInfo.avgmean.toFixed(4) }} mg/kg
+            </li>
+            <li style="margin-bottom: 8px; font-size: 14px;">
+              <strong>镉输入通量:</strong>{{ currentFeatureInfo.lncd.toFixed(4) }} g/ha/a
+              <!-- 若有单位可补充:{{ currentFeatureInfo.lncdUnit }} -->
+            </li>
+            <li style="margin-bottom: 8px; font-size: 14px;">
+              <strong>镉输出通量:</strong>{{ currentFeatureInfo.outcd.toFixed(4) }} g/ha/a
+            </li>
+            <li style="margin-bottom: 8px; font-size: 14px;">
+              <strong>镉净通量:</strong>{{ currentFeatureInfo.netcd.toFixed(4) }} g/ha/a
+            </li>
+            <li style="margin-bottom: 8px; font-size: 14px;">
+              <strong>当年镉浓度:</strong>{{ currentFeatureInfo.endcd.toFixed(4) }} g/ha/a
+            </li>
+          </ul>
         </div>
         <div v-if="!loadingTownshipMap && !townshipMapInstance" class="no-data">
           <el-icon><Location /></el-icon>
           <p>暂无乡镇边界数据</p>
         </div>
+        <div v-if="loadingMap" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>地图加载中...</span>
+        </div>
+        <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="水稻态Cd预测地图" class="map-image">
+        <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+          <el-icon><Picture /></el-icon>
+          <p>暂无地图数据</p>
+        </div>
+
       </div>
 
        <!-- 统计图表区域 -->
       <div class="stats-area">
         <h3>水稻Cd预测统计信息</h3>
         <div class="model-info">
-          <el-tag type="info">{{ modelInfo.modelType || 'Cd预测模型' }}</el-tag>
+          <el-tag type="info">{{ '水稻Cd预测模型' }}</el-tag>
           <span class="update-time">
             最后更新: {{ modelInfo.updateTime ? new Date(modelInfo.updateTime).toLocaleString() : '未知' }}
           </span>
@@ -113,21 +130,7 @@
         </div>
         
         <div v-if="!loadingStats && statisticsData.length" class="stats-container">
-          <!-- 基础统计表格 -->
-          <h4>基础统计信息</h4>
-          <el-table 
-            :data="statisticsData" 
-            style="width: 100%; margin-bottom: 20px;"
-            border
-            stripe
-          >
-            <el-table-column prop="name" label="统计项" min-width="180" />
-            <el-table-column prop="value" label="值" min-width="150" />
-            <el-table-column prop="unit" label="单位" min-width="100" />
-            <el-table-column prop="description" label="描述" min-width="200" />
-          </el-table>
-
-          <!-- 分布统计表格 -->
+           <!-- 分布统计表格 -->
           <h4>水稻镉含量分布统计</h4>
           <el-table 
             :data="distributionData" 
@@ -145,6 +148,20 @@
               </template>
             </el-table-column>
           </el-table>
+          
+          <!-- 基础统计表格 -->
+          <h4>基础统计信息</h4>
+          <el-table 
+            :data="statisticsData" 
+            style="width: 100%; margin-bottom: 20px;"
+            border
+            stripe
+          >
+            <el-table-column prop="name" label="统计项" min-width="180" />
+            <el-table-column prop="value" label="值" min-width="150" />
+            <el-table-column prop="unit" label="单位" min-width="100" />
+            <el-table-column prop="description" label="描述" min-width="200" />
+          </el-table>
         </div>
         
         <div v-if="!loadingStats && !statisticsData.length" class="no-data">
@@ -161,7 +178,7 @@
           <el-icon class="loading-icon"><Loading /></el-icon>
           <span>直方图加载中...</span>
         </div>
-        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="作物态Cd预测直方图" class="histogram-image">
+        <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="水稻Cd预测直方图" class="histogram-image">
         <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
           <el-icon><Histogram /></el-icon>
           <p>暂无直方图数据</p>
@@ -192,6 +209,7 @@ export default {
   },
   data() {
     return {
+      currentFeatureInfo: null, // 存储当前点击要素的详细信息
       mapContainerStyle: {
       width: '100%',
       height: '800px'
@@ -226,6 +244,8 @@ export default {
       currentTooltipData: null, // 当前乡镇的详情数据
       isTooltipLoading: false, // tooltip 是否在加载中
       dataMap:{},
+      fluxDataMap: {}, // 存储通量Cd数据
+      fluxDataLoaded: false, // 通量Cd数据是否已加载
     };
   },
 
@@ -233,6 +253,7 @@ export default {
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
     this.fetchStatistics();
+    this.fetchFluxData();
 
     // 使用更智能的初始化时机
     this.$nextTick(() => {
@@ -256,6 +277,7 @@ export default {
     // 组件销毁前移除事件监听,避免内存泄漏
     window.removeEventListener('resize', this.handleResize);
     if (this.townshipMapInstance) {
+      this.townshipMapInstance.off('click', this.handleWmsFeatureClick);
       this.townshipMapInstance.remove();
       this.townshipMapInstance = null;
     }
@@ -263,6 +285,24 @@ export default {
 
 
   methods: {
+    async fetchFluxData() {
+      try {
+        this.loadingTownshipMap = true;
+        const response = await api8000.get('/api/cd-flux/statistics/town/all');
+        
+        if (response.data && response.data.success) {
+          this.fluxDataMap = response.data.data.statistics || {};
+          this.fluxDataLoaded = true;
+          console.log('通量Cd数据加载成功', this.fluxDataMap);
+        }
+      } catch (error) {
+        console.error('获取通量Cd数据失败:', error);
+        this.fluxDataMap = {};
+      } finally {
+        this.loadingTownshipMap = false;
+      }
+    },
+
     handleResize() {
       if (this.townshipMapInstance) {
         this.townshipMapInstance.invalidateSize();
@@ -430,25 +470,6 @@ export default {
       }
     },
 
-    // 根据Cd含量获取对应的颜色
-    getColorByCdValue(cdValue) {
-      if (cdValue === null || cdValue === undefined) {
-        return '#CCCCCC'; // 无数据时显示灰色
-      }
-      const numValue = Number(cdValue);
-      if (isNaN(numValue)) {
-        return '#CCCCCC';
-      }
-      if (numValue >= 0.0 && numValue < 0.2) {
-        return '#00FF00'; // 安全区间 - 绿色
-      } else if (numValue >= 0.2 && numValue < 0.3) {
-        return '#FFFF00'; // 预警区间 - 黄色
-      } else if (numValue >= 0.3) {
-        return '#FF0000'; // 超标区间 - 红色
-      }
-      return '#CCCCCC'; // 默认灰色
-    },
-
     // 根据乡镇名请求接口
     async fetchTownshipDetailByName(townName) {
       if (this.dataMap[townName]) {
@@ -472,6 +493,9 @@ export default {
 async initTownshipMap() {
     try {
       this.loadingTownshipMap = true;
+      if (!this.fluxDataLoaded) {
+        await this.fetchFluxData();
+      }
       
       // 先强制设置容器尺寸
       this.forceMapContainerSize();
@@ -655,32 +679,30 @@ renderTownshipMap() {
       attributionControl: false
     });
 
-    // 添加缩放控件
+   // 添加缩放控件
     L.control.zoom({
       position: 'bottomright'
     }).addTo(this.townshipMapInstance);
 
+    // 添加比例尺控件
+    L.control.scale({
+      position: 'bottomleft',
+      imperial: false // 使用公制单位
+    }).addTo(this.townshipMapInstance);
+
     // 添加空白底图
     L.tileLayer('').addTo(this.townshipMapInstance);
 
     // 处理GeoJSON数据
     const geoJsonLayer = L.geoJSON(this.townshipGeoJson, {
-      style: (feature) => {
-        const townName = feature.properties.name;
-        const townData = this.dataMap[townName];
-        let avgValue = null;
-        
-        if (townData?.data?.基础统计) {
-          avgValue = Number(townData.data.基础统计.平均值);
-        }
-        
+      style: () => {
         return {
-          fillColor: this.getColorByCdValue(avgValue),
+          fillColor: 'transparent', // 固定为白色填充
           weight: 2,
           opacity: 1,
-          color: '#000',
+          color: '#000', // 黑色边界线
           dashArray: '',
-          fillOpacity: 0.7
+          fillOpacity: 0
         };
       },
       onEachFeature: (feature, layer) => {
@@ -688,13 +710,15 @@ renderTownshipMap() {
         
         // 保存原始样式,用于恢复
         const originalStyle = {
+          fillColor: 'transparent', // 固定为白色填充
           weight: 2,
-          color: '#000',
+          opacity: 1,
+          color: '#000', // 黑色边界线
           dashArray: '',
-          fillOpacity: 0.7,
-          fillColor: this.getColorByCdValue(this.dataMap[townName]?.data?.基础统计?.平均值)
+          fillOpacity: 0
         };
         
+        
         // 鼠标悬停事件 - 修复版本
         layer.on('mouseover', (e) => {
           // 高亮当前区域
@@ -719,14 +743,18 @@ renderTownshipMap() {
             layer.closeTooltip();
           }
         });
-        
-        // 点击事件
-        layer.on('click', () => {
-          this.$message.info(`${townName} 的Cd含量数据已显示`);
-        });
       }
     }).addTo(this.townshipMapInstance);
 
+    // 添加GeoServer WMS图层作为底图
+    const wmsLayer = L.tileLayer.wms('http://localhost:8080/geoserver/soilgd/wms', {
+      layers: 'soilgd:shapeMap', // 替换为你的图层名称
+      format: 'image/png',
+      transparent: true,
+      version: '1.1.0',
+      attribution: 'GeoServer WMS'
+    }).addTo(this.townshipMapInstance);
+
     // 调整地图视野
     if (geoJsonLayer.getBounds().isValid()) {
       // 方式1:完全禁用 fitBounds,直接设置中心和zoom
@@ -744,6 +772,7 @@ renderTownshipMap() {
     }
     
     console.log('地图渲染完成');
+    this.townshipMapInstance.on('click', this.handleWmsFeatureClick);
 
   } catch (error) {
     console.error('渲染地图时发生错误:', error);
@@ -751,48 +780,131 @@ renderTownshipMap() {
   }
 },
 
-  // 新增:显示tooltip的方法
-// 修复显示tooltip的方法
-showTooltip(layer, townName, latlng) {
-  const townData = this.dataMap[townName];
-  let tooltipContent = `<div class="town-tooltip"><h3>${townName}</h3>`;
-  
-  if (!townData?.data?.基础统计) {
-    tooltipContent += '<p>数据加载中...</p></div>';
-  } else {
-    const stats = townData.data.基础统计;
-    const dist = townData.data.分布统计表格;
-    tooltipContent += `
-      <div style="height:1px;background:#0066CC;margin:5px 0;"></div>
-      <p><strong>样本数量:</strong> ${stats.采样点数量 ?? '无数据'}</p>
-      <p><strong>平均值:</strong> ${stats.平均值?.toFixed(4) ?? '无数据'} mg/kg</p>
-      <p><strong>最小值:</strong> ${stats.最小值?.toFixed(4) ?? '无数据'} mg/kg</p>
-      <p><strong>最大值:</strong> ${stats.最大值?.toFixed(4) ?? '无数据'} mg/kg</p>
-      <div style="height:1px;background:#0066CC;margin:5px 0;"></div>
-      <p><strong>安全区间占比:</strong> ${dist?.汇总?.安全区间占比 ?? '无'}%</p>
-      <p><strong>预警区间占比:</strong> ${dist?.汇总?.预警区间占比 ?? '无'}%</p>
-      <p><strong>超标区间占比:</strong> ${dist?.汇总?.超标区间占比 ?? '无'}%</p>
-    </div>`;
-  }
-  
-  // 先解除之前的tooltip绑定
-  if (layer._tooltip) {
-    layer.unbindTooltip();
+
+async handleWmsFeatureClick(e) {
+  try {
+    const { lat, lng } = e.latlng;
+    const mapSize = this.townshipMapInstance.getSize();
+
+    // 1. 转换坐标:地理坐标 → 地图像素坐标(Leaflet 默认 EPSG:4326)
+    const containerPoint = this.townshipMapInstance.latLngToContainerPoint([lat, lng]);
+    const x = Math.round(containerPoint.x);
+    const y = Math.round(containerPoint.y);
+
+    // 2. 构造 GetFeatureInfo 请求 URL(强制 JSON + 修正参数)
+    const wmsBaseUrl = 'http://localhost:8080/geoserver/soilgd/wms'; // 确保工作区和服务名正确
+    const params = new URLSearchParams({
+      SERVICE: 'WMS',
+      VERSION: '1.1.1', // 建议用 1.1.1(GeoServer 更兼容)
+      REQUEST: 'GetFeatureInfo',
+      LAYERS: 'soilgd:shapeMap',
+      QUERY_LAYERS: 'soilgd:shapeMap',
+      FORMAT: 'image/png',
+      TRANSPARENT: 'true',
+      INFO_FORMAT: 'application/json', // 强制返回 JSON
+      bbox: this.townshipMapInstance.getBounds().toBBoxString(),
+      X: x,
+      Y: y,
+      WIDTH: mapSize.x,
+      HEIGHT: mapSize.y,
+      SRS: 'EPSG:4326', // 与地图 CRS 一致
+      FEATURE_COUNT: 1 // 限制返回 1 个要素(关键:确保日志中 FeatureCount=1)
+    });
+
+    // 打印完整请求 URL(用于调试)
+    const requestUrl = `${wmsBaseUrl}?${params.toString()}`;
+    console.log('GetFeatureInfo 完整请求 URL:', requestUrl);
+
+    // 3. 发送请求并解析 JSON
+    const response = await fetch(requestUrl);
+    if (!response.ok) throw new Error(`请求失败:${response.status} ${response.statusText}`);
+
+    // 4. 验证响应类型(必须为 JSON)
+    const contentType = response.headers.get('Content-Type');
+    if (!contentType.includes('application/json')) {
+      throw new Error(`非 JSON 响应:${contentType},请检查 GeoServer 配置`);
+    }
+    const featureData = await response.json();
+
+    if (!featureData?.features || !Array.isArray(featureData.features) || featureData.features.length === 0) {
+      this.$message.warning('该区域无要素数据');
+      return; // 无要素时直接返回,不继续处理
+    }
+
+    const properties = featureData.features[0].properties || {};
+      this.currentFeatureInfo = {
+        avgmean: properties._avgmean,       // 平均镉含量
+        lncd: properties.Ln_Cd,             // 输入镉通量
+        outcd: properties.Out_Cd,           // 输出镉通量
+        netcd: properties.Net_Cd,           // 净镉通量
+        endcd: properties.End_Cd            // 最终镉浓度
+      };
+      
+      // 打印验证(可选)
+      console.log('当前点击要素信息:', this.currentFeatureInfo);
+    console.log('WMS 要素点击信息(JSON):', featureData);
+  } catch (error) {
+    console.error('WMS 要素点击交互失败:', error);
+    this.$message.warning('获取要素信息失败:' + error.message);
   }
-  
-  // 使用Leaflet的tooltip
-  layer.bindTooltip(tooltipContent, {
-    className: 'custom-town-tooltip',
-    direction: 'top',
-    permanent: false,
-    sticky: true,
-    offset: [0, -10],
-    interactive: false // 确保tooltip不会拦截鼠标事件
-  });
-  
-  // 打开tooltip
-  layer.openTooltip(latlng);
 },
+
+  // 新增:显示tooltip的方法
+    showTooltip(layer, townName, latlng) {
+      const riceData = this.dataMap[townName]; // 水稻Cd数据
+      const fluxData = this.fluxDataMap[townName]; // 通量Cd数据
+      
+      let tooltipContent = `<div class="town-tooltip"><h3>${townName}</h3>`;
+      
+      // 水稻Cd数据部分
+      tooltipContent += `<div style="margin-bottom: 10px;"><strong>水稻Cd</strong></div>`;
+      if (!riceData?.data?.基础统计) {
+        tooltipContent += '<p>水稻Cd数据加载中...</p>';
+      } else {
+        const stats = riceData.data.基础统计;
+        const dist = riceData.data.分布统计表格;
+        tooltipContent += `
+          <p><strong>样本数量:</strong> ${stats.采样点数量 ?? '无数据'}</p>
+          <p><strong>平均值:</strong> ${stats.平均值?.toFixed(4) ?? '无数据'} mg/kg</p>
+          <p><strong>安全区间占比:</strong> ${dist?.汇总?.安全区间占比 ?? '无'}</p>
+          <p><strong>预警区间占比:</strong> ${dist?.汇总?.预警区间占比 ?? '无'}</p>
+          <p><strong>超标区间占比:</strong> ${dist?.汇总?.超标区间占比 ?? '无'}</p>
+        `;
+      }
+      
+      // 通量Cd数据部分
+      tooltipContent += `<div style="margin-top: 10px; margin-bottom: 10px;"><strong>通量Cd</strong></div>`;
+      if (!fluxData) {
+        tooltipContent += '<p>通量Cd数据加载中...</p>';
+      } else {
+        tooltipContent += `
+          <p><strong>输入通量:</strong> ${fluxData.in_cd?.mean?.toFixed(4) ?? '无数据'} ${fluxData.in_cd?.unit ?? ''}</p>
+          <p><strong>输出通量:</strong> ${fluxData.out_cd?.mean?.toFixed(4) ?? '无数据'} ${fluxData.out_cd?.unit ?? ''}</p>
+          <p><strong>净通量:</strong> ${fluxData.net_cd?.mean?.toFixed(4) ?? '无数据'} ${fluxData.net_cd?.unit ?? ''}</p>
+          <p><strong>土壤Cd浓度:</strong> ${fluxData.end_cd?.mean?.toFixed(4) ?? '无数据'} ${fluxData.end_cd?.unit ?? ''}</p>
+        `;
+      }
+      
+      tooltipContent += `</div>`;
+      
+      // 先解除之前的tooltip绑定
+      if (layer._tooltip) {
+        layer.unbindTooltip();
+      }
+      
+      // 使用Leaflet的tooltip
+      layer.bindTooltip(tooltipContent, {
+        className: 'custom-town-tooltip',
+        direction: 'top',
+        permanent: false,
+        sticky: true,
+        offset: [0, -10],
+        interactive: false
+      });
+      
+      // 打开tooltip
+      layer.openTooltip(latlng);
+    },
     
     // 上传并计算
     async calculate() {
@@ -812,7 +924,7 @@ showTooltip(layer, townName, latlng) {
         formData.append('data_file', this.selectedFile);
         formData.append('use_database', 'false');
         
-        // 调用作物Cd地图接口
+        // 调用水稻Cd地图接口
         const mapResponse = await api8000.post(
           '/api/cd-prediction/crop-cd/generate-and-get-map',
           formData,
@@ -869,7 +981,7 @@ showTooltip(layer, townName, latlng) {
         formData.append('area', this.countyName);
         formData.append('use_database', 'true');
         
-        // 调用作物Cd地图接口
+        // 调用水稻Cd地图接口
         const mapResponse = await api8000.post(
           '/api/cd-prediction/crop-cd/generate-and-get-map',
           formData,
@@ -923,7 +1035,7 @@ showTooltip(layer, townName, latlng) {
       
       const link = document.createElement('a');
       link.href = URL.createObjectURL(this.mapBlob);
-      link.download = `${this.countyName}_作物态Cd预测地图.jpg`;
+      link.download = `${this.countyName}_水稻Cd预测地图.jpg`;
       link.click();
       URL.revokeObjectURL(link.href);
     },
@@ -937,7 +1049,7 @@ showTooltip(layer, townName, latlng) {
       
       const link = document.createElement('a');
       link.href = URL.createObjectURL(this.histogramBlob);
-      link.download = `${this.countyName}_作物态Cd预测直方图.jpg`;
+      link.download = `${this.countyName}_水稻Cd预测直方图.jpg`;
       link.click();
       URL.revokeObjectURL(link.href);
     },
@@ -945,7 +1057,7 @@ showTooltip(layer, townName, latlng) {
     // 导出数据
     async exportData() {
       try {
-        this.$message.info('正在获取作物态Cd预测数据...');
+        this.$message.info('正在获取水稻态Cd预测数据...');
         
         const response = await api8000.get(
           `/api/cd-prediction/crop-cd/export-csv`,
@@ -955,7 +1067,7 @@ showTooltip(layer, townName, latlng) {
         const blob = new Blob([response.data], { type: 'text/csv' });
         const link = document.createElement('a');
         link.href = URL.createObjectURL(blob);
-        link.download = `作物态Cd预测数据.csv`;
+        link.download = `水稻态Cd预测数据.csv`;
         link.click();
         URL.revokeObjectURL(link.href);
         
@@ -983,7 +1095,7 @@ showTooltip(layer, townName, latlng) {
 
 .township-map-wrapper {
   position: relative;
-  width: 100%;
+  width: 90%;
   max-width: 1000px;
   margin: 15px auto;
   border: 1px solid #e0e0e0;
@@ -1028,28 +1140,44 @@ showTooltip(layer, townName, latlng) {
 }
 
 .town-tooltip {
-  min-width: 200px;
-  max-width: 300px;
+  min-width: 280px;
+  max-width: 350px;
 }
 
 .town-tooltip h3 {
-  margin: 0 0 5px;
+  margin: 0 0 10px;
   color: #0066CC;
   text-align: center;
   font-size: 16px;
+  border-bottom: 2px solid #0066CC;
+  padding-bottom: 5px;
+}
+
+.town-tooltip strong {
+  color: #333;
+  font-weight: 600;
 }
 
 .town-tooltip p {
-  margin: 2px 0;
+  margin: 3px 0;
   font-size: 12px;
   line-height: 1.4;
 }
 
+/* 区分不同数据部分的样式 */
+.town-tooltip div strong {
+  color: #47C3B9;
+  display: block;
+  margin: 8px 0 5px;
+  padding-left: 5px;
+  border-left: 3px solid #47C3B9;
+}
+
 /* Leaflet地图容器样式 */
 .township-map-container {
   width: 100% !important;
   max-width: 1000px;
-  height: 800px !important;
+  height: 500px !important;
   border-radius: 4px;
   background-color: #f5f5f5;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@@ -1263,4 +1391,61 @@ showTooltip(layer, townName, latlng) {
     height: 500px !important;
   }
 }
+
+/* 地图容器包裹层:确保内部元素垂直排列 */
+.township-map-wrapper {
+  position: relative;
+  width: 90%;
+  max-width: 1000px;
+  margin: 15px auto;
+  border: 1px solid #e0e0e0;
+  border-radius: 8px;
+  background: white;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+  overflow: hidden;
+  display: flex; /* 新增:包裹层使用 Flex 布局 */
+  flex-direction: column; /* 子元素垂直排列 */
+}
+
+/* 要素信息面板:水平平铺 */
+.feature-info-panel {
+  background-color: #f8f9fa;
+  border: 1px solid #e9ecef;
+  border-radius: 8px;
+  padding: 15px;
+  margin-top: 15px; /* 与地图容器间距 */
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  width: 100%; /* 占满包裹层宽度 */
+}
+
+/* 信息面板标题:占满宽度 */
+.feature-info-panel h4 {
+  width: 100%;
+  margin-bottom: 10px; /* 标题与内容间距 */
+}
+
+/* 信息列表:水平排列 */
+.feature-info-panel ul {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  gap: 15px; /* 项之间的间距 */
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+/* 信息项:水平平铺 */
+.feature-info-panel li {
+  flex: 1; /* 每项占据等宽空间 */
+  min-width: 150px; /* 最小宽度,防止过窄 */
+  margin-bottom: 0; /* 移除垂直间距 */
+  font-size: 14px;
+  color: #333;
+}
+
+/* 强调文字样式 */
+.feature-info-panel li strong {
+  color: #0066CC;
+  margin-right: 5px;
+}
 </style>

+ 437 - 0
src/views/User/cadmiumPrediction/FutureCadmiumPrediction.vue

@@ -0,0 +1,437 @@
+<template>
+  <div class="container">
+    <!-- 顶部操作栏 -->
+    <div class="toolbar">
+      <div class="forecast-controls">
+        <div class="year-selector">
+          <!-- 新增:年份输入标题 -->
+          <span class="input-label">预测年份:</span>
+          <el-input
+            v-model.number="forecastYear"
+            type="number"
+            placeholder="输入预测年份(1-100)"
+            :min="1"
+            :max="100"
+            @keypress.enter="generateForecast"
+            :class="{ 'is-invalid': !isValidYear }"
+            class="custom-input"
+          ></el-input>
+          <el-button 
+            class="custom-button"
+            :loading="isGenerating"
+            :disabled="(!isValidYear || isGenerating)"
+            @click="generateForecast"
+          >
+            开始预测
+          </el-button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 主体内容区 -->
+    <div class="content-area">
+      <!-- 预测结果展示区 - 垂直排列 -->
+        <!-- 地图部分 -->
+        <div class="visualization-section">
+          <h3>土壤Cd未来浓度预测地图</h3>
+          <div class="image-container">
+          <div v-if="loadingMap" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>地图加载中...</span>
+          </div>
+          <img v-if="mapImageUrl" :src="mapImageUrl" class="result-img"></img>
+          <div v-else class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无地图数据</p>
+          </div>
+        </div>
+        </div>
+
+        <!-- 直方图部分 -->
+        <div class="visualization-section">
+          <h3>预测直方图</h3>
+          <div class="image-container">
+          <div v-if="loadingHistogram" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>直方图加载中...</span>
+          </div>
+          <img v-if="histogramImageUrl" :src="histogramImageUrl" class="result-img"></img>
+          <div v-else class="no-data">
+            <el-icon><Histogram /></el-icon>
+            <p>暂无直方图数据</p>
+          </div>
+        </div>
+      </div>
+      </div>
+  </div>
+</template>
+
+<script>
+import { api8000 } from '@/utils/request';
+import { Loading, Picture, Histogram, Right } from '@element-plus/icons-vue';
+
+export default {
+  name: 'FutureCdPrediction',
+  components: {
+    Loading,
+    Picture,
+    Histogram,
+    Right
+  },
+  props: {
+    countyName: {
+      type: String,
+      required: true,
+      default: '乐昌市'
+    }
+  },
+  data() {
+    return {
+      forecastYear: null,
+      isGenerating: false,
+      generateVisualization: true,
+      loadingMap: false,
+      loadingHistogram: false,
+      mapBlob: null,
+      histogramBlob: null,
+      mapImageUrl: '',
+      histogramImageUrl: ''
+    };
+  },
+  computed: {
+    isValidYear() {
+      return this.forecastYear >= 1 && this.forecastYear <= 100;
+    },
+    canGenerate() {
+      return this.isValidYear && !this.isGenerating;
+    }
+  },
+  watch: {
+    forecastYear(newVal) {
+      newVal = parseInt(newVal);
+      if (isNaN(newVal)) this.forecastYear = null;
+      if (newVal < 1) this.forecastYear = 1;
+      if (newVal > 100) this.forecastYear = 100;
+    }
+  },
+  methods: {
+    // 预测控制逻辑
+    async generateForecast() {
+      if (this.isGenerating) return;
+
+      this.isGenerating = true;
+      this.mapBlob = null;
+      this.histogramBlob = null;
+      this.mapImageUrl = '';
+      this.histogramImageUrl = '';
+
+      try {
+        // 1. 尝试加载现有预测数据
+        await this.checkExistingData();
+
+        // 2. 如果存在历史数据,直接显示
+        this.$message.success(`成功加载${this.forecastYear}年预测数据`);
+        this.isGenerating = false;
+        return;
+      } catch (loadError) {
+        console.log('未找到历史数据,开始生成预测...', loadError);
+      }
+
+      try {
+        // 3. 生成新预测
+        await this.generateNewPrediction();
+        
+        // 4. 加载新生成的数据
+        await Promise.all([
+          this.loadVisualization('map', this.forecastYear),
+          this.loadVisualization('histogram', this.forecastYear)
+        ]);
+
+        this.$message.success(`预测生成成功(年份:${this.forecastYear})`);
+      } catch (generateError) {
+        this.$message.error(`预测失败:${generateError.message}`);
+      } finally {
+        this.isGenerating = false;
+      }
+    },
+
+    // 检查历史数据是否存在
+    async checkExistingData() {
+      await Promise.all([
+        this.loadVisualization('map', this.forecastYear),
+        this.loadVisualization('histogram', this.forecastYear)
+      ]);
+    },
+
+    // 生成新预测
+    async generateNewPrediction() {
+      let url = `/api/cd-flux/predict-future-cd?years=${encodeURIComponent(
+        this.forecastYear
+      )}`;
+      if (this.countyName) {
+        url += `&area=${encodeURIComponent(this.countyName)}`;
+      }
+      if (this.generateVisualization) {
+        url += '&generate_visualization=true';
+      }
+
+      const response = await api8000.get(url, { responseType: 'json' });
+      if (!response.data.success) {
+        throw new Error(response.data.message || '生成预测失败');
+      }
+    },
+
+    // 加载可视化数据
+    async loadVisualization(type, year) {
+      try {
+        const response = await api8000.get(
+          `/api/cd-flux/predict-future-cd/${type}/${year}`,
+          { responseType: 'blob' }
+        );
+
+        if (type === 'map') {
+          this.mapBlob = response.data;
+          this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+        } else {
+          this.histogramBlob = response.data;
+          this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+        }
+      } catch (error) {
+        // 显式抛出错误供外部捕获
+        throw new Error(`加载${type}失败: ${error.message}`);
+      }
+    },
+  }
+};
+</script>
+
+<style scoped>
+/* 自定义覆盖样式 */
+.container {
+  padding: 20px;
+  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;
+}
+
+/* 结果展示区 - 垂直排列 */
+.content-area {
+  display: flex;
+  flex-direction: column;
+  gap: 25px;
+  margin-top: 15px;
+}
+
+/* 可视化区域通用样式 */
+.visualization-section {
+  background-color: rgba(255, 255, 255, 0.85);
+  border-radius: 10px;
+  padding: 20px;
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
+  position: relative;
+  backdrop-filter: blur(5px);
+  overflow: hidden;
+  transition: all 0.3s ease;
+  border: 1px solid rgba(100, 180, 255, 0.2);
+}
+
+.visualization-section:hover {
+  box-shadow: 0 6px 20px rgba(0, 100, 200, 0.12);
+  transform: translateY(-3px);
+}
+
+.visualization-section h3 {
+  margin-top: 0;
+  margin-bottom: 15px;
+  color: #2c3e50;
+  font-size: 1.3rem;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #eaeaea;
+  display: flex;
+  align-items: center;
+}
+
+.visualization-section h3 i {
+  margin-right: 8px;
+  color: #47C3B9;
+}
+
+/* 图片容器样式 */
+.image-container {
+  position: relative;
+  min-height: 350px;
+  border-radius: 6px;
+  overflow: hidden;
+  background-color: #f8fafc;
+  border: 1px dashed #cbd5e0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.result-img {
+  width: 100%;
+  max-height: 500px;
+  object-fit: contain;
+  border-radius: 4px;
+  transition: opacity 0.3s;
+}
+
+/* 加载状态样式 */
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  color: #4a5568;
+}
+
+.loading-icon {
+  font-size: 2rem;
+  margin-bottom: 10px;
+  color: #47C3B9;
+  animation: spin 1.5s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 无数据状态样式 */
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  color: #a0aec0;
+  text-align: center;
+  padding: 20px;
+}
+
+.no-data p {
+  margin-top: 10px;
+  font-size: 1.1rem;
+}
+
+/* 工具栏样式增强 */
+.toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 20px;
+  background-color: rgba(255, 255, 255, 0.9);
+  border-radius: 10px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+  backdrop-filter: blur(5px);
+  border: 1px solid rgba(100, 180, 255, 0.2);
+}
+
+.forecast-controls {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px;
+  align-items: center;
+}
+
+.year-selector {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px;
+  align-items: center;
+  flex: 1;
+}
+
+.custom-input {
+  flex: 1;
+  min-width: 200px;
+}
+
+.custom-input.is-invalid {
+  border-color: #fc8181;
+}
+
+.custom-button {
+  background: linear-gradient(135deg, #47C3B9 0%, #3ba0a0 100%) !important;
+  color: white !important;
+  border: none;
+  border-radius: 15px;
+  padding: 10px 25px;
+  font-weight: bold;
+  transition: all 0.3s ease;
+  box-shadow: 0 4px 6px rgba(71, 195, 185, 0.2);
+}
+
+.custom-button:hover:not(:disabled) {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 8px rgba(71, 195, 185, 0.3);
+}
+
+.custom-button:disabled {
+  opacity: 0.7;
+  cursor: not-allowed;
+}
+
+/* 响应式设计 */
+@media (min-width: 992px) {
+  .content-area {
+    flex-direction: row;
+    flex-wrap: wrap;
+  }
+  
+  .visualization-section {
+    flex: 1;
+    min-width: calc(50% - 15px);
+  }
+}
+
+@media (max-width: 768px) {
+  .toolbar {
+    padding: 15px;
+  }
+  
+  .visualization-section {
+    padding: 15px;
+  }
+  
+  .image-container {
+    min-height: 300px;
+  }
+}
+
+/* 结果提示动画 */
+.fade-enter-active, .fade-leave-active {
+  transition: opacity 0.5s;
+}
+.fade-enter, .fade-leave-to {
+  opacity: 0;
+}
+
+/* 新增:输入栏标题样式 */
+.input-label {
+  font-size: 14px;
+  color: #666;
+  white-space: nowrap; /* 禁止换行,保持标签与输入框对齐 */
+  margin-right: 8px; /* 与输入框的间距 */
+}
+
+/* 新增:限定输入框长度 */
+.custom-input {
+  flex: 1;
+  min-width: 200px; /* 最小宽度,避免过窄 */
+  max-width: 320px; /* 最大宽度,限制输入栏长度 */
+}
+
+/* 调整按钮与输入框的间距(可选) */
+.custom-button {
+  margin-left: auto; /* 让按钮靠右(若需要) */
+  /* 原有按钮样式不变 */
+}
+</style>

+ 35 - 22
src/views/login/loginView.vue

@@ -311,7 +311,8 @@ const onSubmit = async () => {
     ElMessage.success(t("login.loginSuccess"));
 
     // 跳转到目标页面
-    await router.push({ name: "samplingMethodDevice1" });
+    await router.push({ name: 'CropCadmiumPrediction' });
+
   } catch (error: any) {
     console.error("登录失败:", {
       error: error.message,
@@ -349,27 +350,39 @@ const onRegister = async () => {
 
     // 注册成功
     if (res.data?.message) {
-      // 显示注册成功消息
-      showMsg(t("register.registerSuccess"), "success");
-      ElMessage.success(t("register.registerSuccess"));
-
-      // 延迟一段时间后自动跳转到登录页并填充信息
-      setTimeout(() => {
-        // 切换到登录表单
-        isLogin.value = true;
-
-        // 将注册的用户名和密码填入登录表单
-        form.name = registerForm.name;
-        form.password = registerForm.password; // 填充密码
-
-        // 清空注册表单
-        registerForm.name = "";
-        registerForm.password = "";
-        registerForm.confirmPassword = "";
-
-        // 可选:给用户一个提示
-        ElMessage.info(t("register.autoLoginPrompt"));
-      }, 1500); // 1.5秒后执行
+      // 注册成功后自动登录
+      try {
+        // 调用登录API
+        const loginResponse = await login({
+          name: registerForm.name,
+          password: registerForm.password,
+          usertype: userType.value,
+        });
+
+        // 检查登录响应结构
+        if (loginResponse.data?.user) {
+          const userData = loginResponse.data.user;
+          
+          // 保存用户信息到store
+          store.saveToken({
+            userId: Number(userData.id),
+            name: userData.name,
+            loginType: userData.userType || userType.value,
+          });
+
+          // 跳转到目标页面
+          await router.push({ name: 'CropCadmiumPrediction' });
+        } else {
+          showErrorMsg(loginResponse.data?.message || '自动登录失败,请手动登录');
+          toggleForm(); // 返回登录页面
+        }
+      } catch (loginError: any) {
+        console.error('自动登录失败:', loginError);
+        showErrorMsg(
+          loginError?.response?.data?.message || '自动登录失败,请手动登录'
+        );
+        toggleForm(); // 返回登录页面
+      }
     } else {
       const errorMsg = res.data?.message || t("register.registerFailed");
       showMsg(errorMsg, "error");