浏览代码

制作输入通量页面

yangtaodemon 5 天之前
父节点
当前提交
72e080efed

+ 1 - 1
.env

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

+ 5 - 4
components.d.ts

@@ -29,9 +29,12 @@ declare module 'vue' {
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
     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']
@@ -39,19 +42,18 @@ declare module 'vue' {
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
-<<<<<<< HEAD
-=======
     ElOption: typeof import('element-plus/es')['ElOption']
->>>>>>> ding
     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']
+    ElTag: typeof import('element-plus/es')['ElTag']
     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']
@@ -65,7 +67,6 @@ declare module 'vue' {
     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']

+ 4 - 4
package-lock.json

@@ -5052,7 +5052,7 @@
       "version": "1.9.16",
       "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.16.tgz",
       "integrity": "sha512-wzZoyySUxkgMZ0ihJ7IaUIblG8Rdc8AbbZKLneyn+QjYsj5q1QU7TEKYqwTr10BGSzY5LI7tJk9Ifo+mEjdFRw==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "@types/geojson": "*"
@@ -5293,7 +5293,7 @@
     },
     "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",
+      "resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.10.1.tgz",
       "integrity": "sha512-RNEDk8TbnwrJl8ujdbKgZRFygLCxd0aBcWLQ05q/pGv4+d0jamE3KXQgQBqGAteE1mbQsk3xoNcqqUgaIGfWVg==",
       "license": "MIT",
       "dependencies": {
@@ -8123,7 +8123,7 @@
     },
     "node_modules/leaflet": {
       "version": "1.9.4",
-      "resolved": "https://registry.npmmirror.com/leaflet/-/leaflet-1.9.4.tgz",
+      "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
       "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
       "license": "BSD-2-Clause"
     },
@@ -9759,7 +9759,7 @@
       "version": "5.6.3",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
       "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
-      "dev": true,
+      "devOptional": true,
       "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",

+ 0 - 4
src/views/User/HmOutFlux/atmosDeposition/airInputFlux.vue

@@ -18,9 +18,6 @@ export default {
 </script>
 
 <style scoped>
-<<<<<<< HEAD
-
-=======
 .atmosphere-flux-container {
   width: 100%;
   max-width: 900px;
@@ -100,5 +97,4 @@ export default {
     min-height: 280px;
   }
 }
->>>>>>> ding
 </style>

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

@@ -201,7 +201,7 @@ export default {
     async fetchLatestMap() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/crop-cd/latest-map/${this.countyName}`,
+          `http://localhost:8000/api/cd-prediction/crop-cd/latest-map/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -217,7 +217,7 @@ export default {
     async fetchLatestHistogram() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/crop-cd/latest-histogram/${this.countyName}`,
+          `http://localhost:8000/api/cd-prediction/crop-cd/latest-histogram/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -396,7 +396,7 @@ export default {
         this.loadingStats = true;
         
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/crop-cd/statistics/${this.countyName}`
+          `http://localhost:8000/api/cd-prediction/crop-cd/statistics/${this.countyName}`
         );
         
         if (response.data.success && response.data.data) {
@@ -440,7 +440,7 @@ export default {
         
         // 调用作物Cd地图接口
         const mapResponse = await axios.post(
-          'https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-map',
+          'http://localhost:8000/api/cd-prediction/crop-cd/generate-and-get-map',
           formData,
           {
             headers: {
@@ -517,7 +517,7 @@ export default {
         this.$message.info('正在获取作物Cd预测数据...');
         
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/download-final-crop-cd-csv`,
+          `http://localhost:8000/api/cd-prediction/download-final-crop-cd-csv`,
           { responseType: 'blob' }
         );
         

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

@@ -84,17 +84,17 @@
         
         <div v-if="!loadingStats && statisticsData.length" class="stats-container">
           <!-- 统计表格 -->
-           <el-table 
-              :data="statisticsData" 
-              style="width: 100%; margin-bottom: 20px;"
-              border
-              stripe
-            >
-              <el-table-column prop="name" label="统计项" min-width="180" />
-              <el-table-column prop="value" label="值" min-width="150" />
-              <el-table-column prop="unit" label="单位" min-width="100" />
-              <el-table-column prop="description" label="描述" min-width="200" />
-            </el-table>
+         <el-table 
+            :data="statisticsData" 
+            style="width: 100%; margin-bottom: 20px;"
+            border
+            stripe
+          >
+            <el-table-column prop="name" label="统计项" min-width="180" />
+            <el-table-column prop="value" label="值" min-width="150" />
+            <el-table-column prop="unit" label="单位" min-width="100" />
+            <el-table-column prop="description" label="描述" min-width="200" />
+          </el-table>
           
           <!-- 统计图表 -->
           <div class="charts-container">
@@ -201,7 +201,7 @@ export default {
     async fetchLatestMap() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
+          `http://localhost:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -217,7 +217,7 @@ export default {
     async fetchLatestHistogram() {
       try {
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
+          `http://localhost:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
           { responseType: 'blob' }
         );
         
@@ -352,7 +352,7 @@ export default {
         },
         yAxis: {
           type: 'value',
-          name: 'Cd含量'
+          name: '(Cd含量)'
         },
         series: [{
           name: '统计值',
@@ -396,7 +396,7 @@ export default {
         this.loadingStats = true;
         
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/effective-cd/statistics/${this.countyName}`
+          `http://localhost:8000/api/cd-prediction/effective-cd/statistics/${this.countyName}`
         );
         
         if (response.data.success && response.data.data) {
@@ -438,9 +438,9 @@ export default {
         formData.append('county_name', this.countyName);
         formData.append('data_file', this.selectedFile);
         
-        // 调用有效Cd地图接口
+        // 调用有效Cd地图接口
         const mapResponse = await axios.post(
-          'https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map',
+          'http://localhost:8000/api/cd-prediction/effective-cd/generate-and-get-map',
           formData,
           {
             headers: {
@@ -511,13 +511,13 @@ export default {
       URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据
+    // 导出数据 - 修改为获取有效Cd的CSV文件
     async exportData() {
       try {
         this.$message.info('正在获取有效Cd预测数据...');
         
         const response = await axios.get(
-          `https://soilgd.com:8000/api/cd-prediction/download-final-effective-cd-csv`,
+          `http://localhost:8000/api/cd-prediction/download-final-effective-cd-csv`,
           { responseType: 'blob' }
         );
         

+ 499 - 99
src/views/User/cadmiumPrediction/totalInputFlux.vue

@@ -1,134 +1,534 @@
 <template>
-  <div class="cd-input-container">
-    <el-card class="gradient-card" shadow="hover">
-      <el-row :gutter="20">
-        <el-col :span="12">
-          <p class="label">大气沉降输入 Cd (g/ha/a)</p>
-          <el-input
-            v-model="atmosphericCd"
-            placeholder="请输入内容"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="12">
-          <p class="label">灌溉水输入 Cd (g/ha/a)</p>
-          <el-input
-            v-model="irrigationCd"
-            placeholder="请输入内容"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="24" style="margin-top: 20px;">
-          <p class="label">农业投入输入 Cd (g/ha/a)</p>
-          <el-input
-            v-model="agriculturalCd"
-            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="toolbar">
+      <!-- 文件上传区域 -->
+      <div class="upload-section">
+        <input type="file" ref="fileInput" accept=".csv" @change="handleFileUpload" style="display: none">
+        <el-button class="custom-button" @click="triggerFileUpload">
+          <el-icon class="upload-icon"><Upload /></el-icon>
+          选择CSV文件
+        </el-button>
+        <span v-if="selectedFile" class="file-name">{{ selectedFile.name }}</span>
+        <el-button 
+          class="custom-button" 
+          :loading="isCalculating" 
+          :disabled="!selectedFile" 
+          @click="calculate"
+        >
+        <el-icon class="upload-icon"><Document /></el-icon>  
+        上传并计算
+        </el-button>
+      </div>
+      <!-- 操作按钮 -->
+      <div class="action-buttons">
+        <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出地图</el-button>
+        <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出直方图</el-button>
+        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容区 -->
+    <div class="content-area">
+      <!-- 地图区域 - 修改为横向布局 -->
+      <div class="horizontal-container">
+        <!-- 地图展示 -->
+        <div class="map-section">
+          <h3>Cd输入通量空间分布图</h3>
+          <div v-if="loadingMap" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>地图加载中...</span>
+          </div>
+          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd输入通量空间分布图" class="map-image">
+          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无地图数据</p>
+          </div>
+        </div>
+        
+        <!-- 直方图展示 -->
+        <div class="histogram-section">
+          <h3>Cd输入通量直方图</h3>
+          <div v-if="loadingHistogram" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>直方图加载中...</span>
+          </div>
+          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd输入通量直方图" class="histogram-image">
+          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+            <el-icon><Histogram /></el-icon>
+            <p>暂无直方图数据</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- 统计图表区域 -->
+      <div class="stats-area">
+        <h3>Cd输入通量统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">Cd通量模型</el-tag>
+          <span class="update-time">
+            最后更新: {{ updateTime ? new Date(updateTime).toLocaleString() : '未知' }}
+          </span>
+        </div>
+        
+        <div v-if="loadingStats" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>统计数据加载中...</span>
+        </div>
+        
+        <div v-if="!loadingStats && statisticsData.length" class="stats-container">
+          <!-- 统计表格 -->
+         <el-table 
+            :data="statisticsData" 
+            style="width: 100%; margin-bottom: 20px;"
+            border
+            stripe
+          >
+            <el-table-column prop="name" label="统计项" min-width="180" />
+            <el-table-column prop="value" label="值" min-width="150" />
+            <el-table-column prop="unit" label="单位" min-width="100" />
+            <el-table-column prop="description" label="描述" min-width="200" />
+          </el-table>
+          
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup>
-import { ref } from 'vue';
-import { ElCard, ElRow, ElCol, ElInput, ElButton } from 'element-plus';
+<script>
+import * as XLSX from 'xlsx';
+import { saveAs } from 'file-saver';
+import axios from 'axios';
+import * as echarts from 'echarts';
+import { 
+  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+} from '@element-plus/icons-vue';
+
+export default {
+  name: 'CdFluxVisualization',
+  components: { 
+    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  },
+  data() {
+    return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
+      loadingStats: false,
+      statisticsData: [],
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      distributionChart: null,
+      updateTime: null
+    };
+  },
+
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+    this.fetchStatistics();
+  },
 
-const atmosphericCd = ref('');
-const irrigationCd = ref('');
-const agriculturalCd = ref('');
+  beforeDestroy() {
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+    if (this.distributionChart) this.distributionChart.dispose();
+  },
+  methods: {
+    // 触发文件选择
+    triggerFileUpload() {
+      this.$refs.fileInput.click();
+    },
+    
+    // 处理文件上传
+    handleFileUpload(event) {
+      const files = event.target.files;
+      if (files && files.length > 0) {
+        this.selectedFile = files[0];
+      } else {
+        this.selectedFile = null;
+      }
+    },
+    
+    // 获取最新结果
+    async fetchLatestResults() {
+      try {
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        
+        // 获取最新地图
+        await this.fetchLatestMap();
+        
+        // 获取最新直方图
+        await this.fetchLatestHistogram();
+        
+      } catch (error) {
+        console.error('获取最新结果失败:', error);
+        this.$message.error('获取最新结果失败');
+      } finally {
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+      }
+    },
+    
+    // 获取最新地图
+    async fetchLatestMap() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/map`,
+          { responseType: 'blob' }
+        );
+        
+        this.mapBlob = response.data;
+        this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+      } catch (error) {
+        console.error('获取最新地图失败:', error);
+        this.$message.warning('获取最新地图失败,请先执行预测');
+      }
+    },
+    
+    // 获取最新直方图
+    async fetchLatestHistogram() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/histogram`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 格式化统计数据
+    formatStatisticsData(stats) {
+      if (!stats) return [];
+      
+      return [
+        { name: '最小值', value: stats.min.toFixed(4), unit: 'g/ha/year', description: '样本中的最小Cd通量' },
+        { name: '最大值', value: stats.max.toFixed(4), unit: 'g/ha/year', description: '样本中的最大Cd通量' },
+        { name: '平均值', value: stats.mean.toFixed(4), unit: 'g/ha/year', description: '所有样本的平均Cd通量' },
+        { name: '标准差', value: stats.std.toFixed(4), unit: 'g/ha/year', description: 'Cd通量的标准差' },
+        { name: '有效像元数', value: stats.valid_pixels, unit: '个', description: '有效数据点的数量' },
+        { name: '总像元数', value: stats.total_pixels, unit: '个', description: '总像元数量' }
+      ];
+    },
 
-const onCalculate = () => {
-  // 暂无计算逻辑,仅作展示
-  alert('计算按钮已点击');
+    // 修改fetchStatistics方法
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/statistics`
+        );
+        
+        if (response.data) {
+          const stats = response.data;
+          this.statisticsData = this.formatStatisticsData(stats);
+          this.updateTime = new Date().toISOString();
+          
+          this.$nextTick(() => {
+            this.initCharts(stats);
+          });
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+    },
+    
+    // 上传并计算
+    async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
+      try {
+        this.isCalculating = true;
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        this.loadingStats = true;
+        
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('csv_file', this.selectedFile);
+        
+        // 调用Cd通量计算接口
+        await axios.post(
+          'http://localhost:8000/api/cd-flux/calculate',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            }
+          }
+        );
+        
+        // 更新后重新获取地图、直方图和统计数据
+        await this.fetchLatestResults();
+        await this.fetchStatistics();
+        
+        this.$message.success('计算完成!');
+        
+      } catch (error) {
+        console.error('计算失败:', error);
+        let errorMessage = '计算失败,请重试';
+        
+        if (error.response) {
+          if (error.response.status === 400) {
+            errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
+          } else if (error.response.status === 500) {
+            errorMessage = '服务器错误:' + (error.response.data.detail || '请稍后重试');
+          }
+        }
+        
+        this.$message.error(errorMessage);
+      } finally {
+        this.isCalculating = false;
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+        this.loadingStats = false;
+      }
+    },
+    
+    // 导出地图
+    exportMap() {
+      if (!this.mapBlob) {
+        this.$message.warning('请先计算生成地图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.mapBlob);
+      link.download = `Cd输入通量空间分布图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出直方图
+    exportHistogram() {
+      if (!this.histogramBlob) {
+        this.$message.warning('请先计算生成直方图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.histogramBlob);
+      link.download = `Cd输入通量直方图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出数据
+    async exportData() {
+      try {
+        this.$message.info('正在获取Cd输入通量数据...');
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/export-csv`,
+          { responseType: 'blob' }
+        );
+        
+        const blob = new Blob([response.data], { type: 'text/csv' });
+        const link = document.createElement('a');
+        link.href = URL.createObjectURL(blob);
+        link.download = `Cd输入通量数据.csv`;
+        link.click();
+        URL.revokeObjectURL(link.href);
+        
+        this.$message.success('数据导出成功');
+      } catch (error) {
+        console.error('导出数据失败:', error);
+        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
+      }
+    }
+  }
 };
 </script>
 
 <style scoped>
-.cd-input-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
+/* 保持原有样式不变 */
+.container {
   padding: 20px;
-}
-
-.gradient-card {
-  /* 半透明渐变背景 */
+  /* 添加70%透明度的渐变背景 */
   background: linear-gradient(
     135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
   );
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
-  padding: 30px;
-  text-align: left; /* 改为左对齐 */
-  width: 600px;
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+.toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-  border: none;
 }
 
-.label {
+.upload-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
+}
+
+.file-name {
+  flex: 1;
+  padding: 0 10px;
+  color: #666;
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
+  border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
   font-weight: bold;
-  font-size: 18px;
-  margin-bottom: 10px; /* 减少底部外边距 */
-  color: #333;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
-.custom-input {
+.content-area {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* 横向布局容器 */
+.horizontal-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
   width: 100%;
-  max-width: 200px;
-  margin-left: 0; /* 确保输入框靠左对齐 */
 }
 
-/* 自定义输入框样式 */
-:deep(.custom-input .el-input__inner) {
-  background: rgba(255, 255, 255, 0.7);
+.map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   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;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.map-image, .histogram-image {
+  width: 100%;
+  height: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
 }
 
-.calculate-btn {
+.table-area {
   width: 100%;
-  max-width: 200px;
-  height: 50px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  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;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #47C3B9;
+}
+
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
+}
+
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
 }
 
-.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);
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
 }
 
-.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;
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
 }
 </style>