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