|
|
@@ -1,36 +1,40 @@
|
|
|
<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="(!isValidYear || isGenerating)"
|
|
|
- @click="generateForecast"
|
|
|
- >
|
|
|
- <el-icon><Right /></el-icon>
|
|
|
- 开始预测
|
|
|
- </el-button>
|
|
|
+ <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="result-display">
|
|
|
<!-- 地图部分 -->
|
|
|
<div class="visualization-section">
|
|
|
- <h3>未来Cd浓度预测地图</h3>
|
|
|
+ <h3>土壤Cd未来浓度预测地图</h3>
|
|
|
+ <div class="image-container">
|
|
|
<div v-if="loadingMap" class="loading-container">
|
|
|
<el-icon class="loading-icon"><Loading /></el-icon>
|
|
|
<span>地图加载中...</span>
|
|
|
@@ -41,10 +45,12 @@
|
|
|
<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>
|
|
|
@@ -56,7 +62,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -200,185 +206,232 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-/* 全局颜色变量 */
|
|
|
-:root {
|
|
|
- --primary-color: #47C3B9;
|
|
|
- --secondary-color: #36A897;
|
|
|
- --text-color: #333;
|
|
|
- --bg-color: #F5F7FA;
|
|
|
- --border-color: #E0E0E0;
|
|
|
- --loading-color: #47C3B9;
|
|
|
- --error-color: #FF4D4F;
|
|
|
+/* 自定义覆盖样式 */
|
|
|
+.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;
|
|
|
}
|
|
|
|
|
|
-/* 容器样式 */
|
|
|
-.container {
|
|
|
- max-width: 1400px;
|
|
|
- margin: 20px auto;
|
|
|
- padding: 0 20px;
|
|
|
- background: var(--bg-color);
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
|
+/* 结果展示区 - 垂直排列 */
|
|
|
+.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);
|
|
|
}
|
|
|
|
|
|
-/* 顶部工具栏 */
|
|
|
-.forecast-controls {
|
|
|
+.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;
|
|
|
- justify-content: space-between;
|
|
|
align-items: center;
|
|
|
- padding: 15px 20px;
|
|
|
- background-color: rgba(230, 247, 255, 0.7);
|
|
|
- border-radius: 8px;
|
|
|
- margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
-/* 预测控制区 */
|
|
|
-.year-selector {
|
|
|
+.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;
|
|
|
- gap: 10px;
|
|
|
align-items: center;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
-/* 自定义按钮 */
|
|
|
-.custom-button {
|
|
|
- padding: 8px 16px;
|
|
|
+.result-img {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 500px;
|
|
|
+ object-fit: contain;
|
|
|
border-radius: 4px;
|
|
|
- font-size: 14px;
|
|
|
- background-color: var(--primary-color) !important;
|
|
|
- color: white !important;
|
|
|
- border: none;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 5px;
|
|
|
+ transition: opacity 0.3s;
|
|
|
}
|
|
|
|
|
|
-.custom-button:hover {
|
|
|
- background-color: #36a897;
|
|
|
+/* 加载状态样式 */
|
|
|
+.loading-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
+ color: #4a5568;
|
|
|
}
|
|
|
|
|
|
-.custom-button:active {
|
|
|
- transform: translateY(0);
|
|
|
+.loading-icon {
|
|
|
+ font-size: 2rem;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ color: #47C3B9;
|
|
|
+ animation: spin 1.5s linear infinite;
|
|
|
}
|
|
|
|
|
|
-.custom-button:disabled {
|
|
|
- background-color: #cccccc !important;
|
|
|
- color: rgba(255,255,255,0.5) !important;
|
|
|
- cursor: not-allowed;
|
|
|
+@keyframes spin {
|
|
|
+ 0% { transform: rotate(0deg); }
|
|
|
+ 100% { transform: rotate(360deg); }
|
|
|
}
|
|
|
|
|
|
-/* 结果展示区 - 垂直布局 */
|
|
|
-.result-display {
|
|
|
+/* 无数据状态样式 */
|
|
|
+.no-data {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 20px;
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
-/* 可视化部分通用样式 */
|
|
|
-.visualization-section {
|
|
|
- background-color: white;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
|
- min-height: 400px;
|
|
|
+/* 工具栏样式增强 */
|
|
|
+.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);
|
|
|
}
|
|
|
|
|
|
-.visualization-section h3 {
|
|
|
- margin-top: 0;
|
|
|
- margin-bottom: 15px;
|
|
|
- font-size: 16px;
|
|
|
- color: var(--text-color);
|
|
|
- border-bottom: 2px solid var(--secondary-color);
|
|
|
- padding-bottom: 5px;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-
|
|
|
-/* 加载状态 */
|
|
|
-.loading-container {
|
|
|
+.forecast-controls {
|
|
|
display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 15px;
|
|
|
align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: var(--loading-color);
|
|
|
- min-height: 300px;
|
|
|
- justify-content: center;
|
|
|
- flex-grow: 1;
|
|
|
}
|
|
|
|
|
|
-/* 无数据提示 */
|
|
|
-.no-data {
|
|
|
+.year-selector {
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 15px;
|
|
|
align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #999;
|
|
|
- min-height: 300px;
|
|
|
- justify-content: center;
|
|
|
- flex-grow: 1;
|
|
|
+ flex: 1;
|
|
|
}
|
|
|
|
|
|
-/* 结果图片 */
|
|
|
-.result-img {
|
|
|
- width: 100%;
|
|
|
- height: 350px;
|
|
|
- object-fit: contain;
|
|
|
- border-radius: 4px;
|
|
|
- margin-top: 10px;
|
|
|
- flex-grow: 1;
|
|
|
- background-color: #f9f9f9;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
+.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);
|
|
|
}
|
|
|
|
|
|
-.result-img:empty {
|
|
|
- display: none;
|
|
|
+.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 (max-width: 992px) {
|
|
|
- .forecast-controls {
|
|
|
- flex-direction: column;
|
|
|
- gap: 15px;
|
|
|
- align-items: stretch;
|
|
|
+@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;
|
|
|
}
|
|
|
|
|
|
- .year-selector {
|
|
|
- flex-direction: column;
|
|
|
- align-items: stretch;
|
|
|
+ .visualization-section {
|
|
|
+ padding: 15px;
|
|
|
}
|
|
|
|
|
|
- .custom-button {
|
|
|
- width: 100%;
|
|
|
+ .image-container {
|
|
|
+ min-height: 300px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* 图标样式 */
|
|
|
-.el-icon {
|
|
|
- color: var(--primary-color);
|
|
|
- font-size: 18px;
|
|
|
+/* 结果提示动画 */
|
|
|
+.fade-enter-active, .fade-leave-active {
|
|
|
+ transition: opacity 0.5s;
|
|
|
+}
|
|
|
+.fade-enter, .fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
}
|
|
|
|
|
|
-/* 输入框样式 */
|
|
|
-.el-input {
|
|
|
- border: 1px solid var(--border-color);
|
|
|
- border-radius: 4px;
|
|
|
- transition: border-color 0.3s;
|
|
|
+/* 新增:输入栏标题样式 */
|
|
|
+.input-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ white-space: nowrap; /* 禁止换行,保持标签与输入框对齐 */
|
|
|
+ margin-right: 8px; /* 与输入框的间距 */
|
|
|
}
|
|
|
|
|
|
-.el-input:focus {
|
|
|
- border-color: var(--primary-color);
|
|
|
- box-shadow: 0 0 5px var(--primary-color);
|
|
|
+/* 新增:限定输入框长度 */
|
|
|
+.custom-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 200px; /* 最小宽度,避免过窄 */
|
|
|
+ max-width: 320px; /* 最大宽度,限制输入栏长度 */
|
|
|
}
|
|
|
|
|
|
-.el-input__inner {
|
|
|
- padding: 8px 12px;
|
|
|
- font-size: 14px;
|
|
|
- color: var(--text-color);
|
|
|
+/* 调整按钮与输入框的间距(可选) */
|
|
|
+.custom-button {
|
|
|
+ margin-left: auto; /* 让按钮靠右(若需要) */
|
|
|
+ /* 原有按钮样式不变 */
|
|
|
}
|
|
|
</style>
|