Преглед на файлове

未来cd预测页面初版

yangtaodemon преди 1 месец
родител
ревизия
4d0393aeec
променени са 4 файла, в които са добавени 280 реда и са изтрити 0 реда
  1. 6 0
      src/components/layout/menuItems.ts
  2. 3 0
      src/locales/zh.json
  3. 7 0
      src/router/index.ts
  4. 264 0
      src/views/User/cadmiumPrediction/FutureCadmiumPrediction.vue

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

@@ -71,6 +71,12 @@ export const menuItems: MenuItem[] = [
     icon: PieChart,
     tab: 'cadmiumPrediction'
   },
+  {
+    index: '/FutureCadmiumPrediction',
+    label: 'FutureCadmiumPrediction.Title',//<!--i18n:EffectiveCadmiumPrediction.Title-->土壤镉有效态含量预测
+    icon: PieChart,
+    tab: 'cadmiumPrediction'
+  },
   {
     index: '/netFlux',
     label: 'netFlux.Title',//<!--i18n:netFlux.Title-->净通量

+ 3 - 0
src/locales/zh.json

@@ -177,6 +177,9 @@
   "CropCadmiumPrediction": {
     "Title": "水稻镉含量预测"
   },
+  "FutureCadmiumPrediction": {
+    "Title": "土壤镉未来含量预测"
+  },
   "cropRiskAssessment": {
     "Title": "水稻镉污染风险"
   },

+ 7 - 0
src/router/index.ts

@@ -337,6 +337,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",

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

@@ -0,0 +1,264 @@
+<template>
+  <div class="container">
+    <!-- 顶部操作栏 -->
+    <div class="forecast-controls">
+      <div class="year-selector">
+        <el-input
+          v-model.number="forecastYear"
+          type="number"
+          placeholder="输入预测年份(1-100)"
+          :min="1"
+          :max="100"
+          @keypress.enter="generateForecast"
+          :class="{ 'is-invalid': !isValidYear }"
+        ></el-input>
+        <el-button 
+          class="custom-button"
+          :loading="isGenerating"
+          :disabled="!canGenerate"
+          @click="generateForecast"
+        >
+          <el-icon class="play-icon"></el-icon>
+          开始预测
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容区 -->
+    <div class="content-area">
+      <!-- 预测结果展示区 -->
+      <div class="result-display">
+        <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 :src=mapImageUrl class="result-img"></img>
+          <div v-if="!loadingMap && !mapImageUrl" class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无地图数据</p>
+        </div>
+        </div>
+        <div class="histogram-section">
+          <h3>预测直方图</h3>
+          <div v-if="loadingHistogram" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>直方图加载中...</span>
+          </div>
+          <img :src=histogramImageUrl class="result-img"></img>
+          <div v-if="!loadingHistogram && !histogramImageUrl" class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无直方图数据</p>
+        </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { SaveAs } from 'file-saver';
+import { api8000 } from '@/utils/request';
+import { 
+  Loading, Picture, Histogram
+} from '@element-plus/icons-vue';
+
+export default {
+  name: 'FutureCdPrediction',
+  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. 构建正确URL(使用GET方法+查询参数)
+      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'
+
+      // 2. 发送GET请求(移除FormData)
+      const predictResponse = await api8000.get(url, { responseType: 'json' })
+
+      if (!predictResponse.data.success) {
+        throw new Error(predictResponse.data.message)
+      }
+
+      // 3. 加载结果(保持原有逻辑)
+      await Promise.all([
+        this.loadVisualization('map', this.forecastYear),
+        this.loadVisualization('histogram', this.forecastYear)
+      ])
+
+      this.$message.success(`预测生成成功(年份:${this.forecastYear})`)
+    } catch (error) {
+      this.$message.error(`预测失败:${error.message}`)
+    } finally {
+      this.isGenerating = false
+    }
+   },
+    
+    // 结果加载逻辑
+    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) {
+        this.$message.error(`加载${type}失败:${error.message}`)
+      }
+    },
+    
+    // 结果导出逻辑
+    exportAllResults() {
+      if (this.mapBlob) SaveAs(this.mapBlob, `future_cd_map_${this.forecastYear}.jpg`)
+      if (this.histogramBlob) SaveAs(this.histogramBlob, `future_cd_histogram_${this.forecastYear}.jpg`)
+    }
+  }
+}
+</script>
+
+
+<style scoped>
+.container {
+  max-width: 1400px;
+  margin: 20px auto;
+  padding: 0 20px;
+}
+
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background-color: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 20px;
+}
+
+.upload-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+}
+
+.forecast-controls {
+  display: flex;
+  gap: 15px;
+  align-items: center;
+}
+
+.year-selector {
+  display: flex;
+  gap: 10px;
+  align-items: center;
+}
+
+.file-name {
+  font-size: 14px;
+  color: #666;
+}
+
+.custom-button {
+  padding: 8px 16px;
+  border-radius: 4px;
+  font-size: 14px;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.result-display {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 20px;
+}
+
+.map-section, .histogram-section {
+  background-color: #fff;
+  padding: 20px;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+}
+
+h3 {
+  margin-top: 0;
+  margin-bottom: 15px;
+  font-size: 16px;
+  color: #333;
+}
+
+.loading-container {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  color: #666;
+}
+
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 10px;
+  color: #999;
+}
+
+.result-img {
+  width: 100%;
+  height: auto;
+  object-fit: contain;
+}
+</style>