refactor: 整体 UI 主题改版为浅色自然风格,并优化文案与清理代码
- 将页面主题从深色科技风改为浅色自然风(土壤棕/叶绿/麦穗黄配色) - 页面标题与文案统一为"种植决策助手",提升可读性 - 移除未使用的导入(pandas、plotly.express、make_subplots) - 更新作物颜色配置以适配新主题 - 调整 Plotly 图表样式(背景、轴线、网格、标注线颜色)适配浅色模式 - 新增 Streamlit 按钮的圆角与悬停样式覆盖 - 删除多余的伪元素装饰代码,精简 CSS
This commit is contained in:
281
app.py
281
app.py
@@ -5,14 +5,11 @@
|
|||||||
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
import plotly.express as px
|
|
||||||
from plotly.subplots import make_subplots
|
|
||||||
|
|
||||||
# ─── Page Config ────────────────────────────────────────────────────────────
|
# ─── Page Config ────────────────────────────────────────────────────────────
|
||||||
st.set_page_config(
|
st.set_page_config(
|
||||||
page_title="农业智能决策系统",
|
page_title="种植决策助手",
|
||||||
page_icon="🌾",
|
page_icon="🌾",
|
||||||
layout="wide",
|
layout="wide",
|
||||||
initial_sidebar_state="expanded",
|
initial_sidebar_state="expanded",
|
||||||
@@ -22,166 +19,175 @@ st.set_page_config(
|
|||||||
st.markdown("""
|
st.markdown("""
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--bg-dark: #0a1628;
|
--soil: #7c5e42;
|
||||||
--bg-card: #0f2040;
|
--leaf: #4a7c59;
|
||||||
--accent-green: #4ade80;
|
--leaf-light: #6b9e75;
|
||||||
--accent-gold: #f59e0b;
|
--wheat: #d4a574;
|
||||||
--accent-blue: #38bdf8;
|
--cream: #faf8f3;
|
||||||
--text-primary: #e2e8f0;
|
--paper: #ffffff;
|
||||||
--text-muted: #64748b;
|
--ink: #2c2c2c;
|
||||||
--border: rgba(74, 222, 128, 0.2);
|
--ink-muted: #5a5a5a;
|
||||||
|
--border: #e5e0d5;
|
||||||
|
--shadow: rgba(0,0,0,0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body, [class*="css"] {
|
html, body, [class*="css"] {
|
||||||
font-family: "PingFang SC", "Microsoft YaHei", "Noto Serif SC", serif;
|
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||||
background-color: var(--bg-dark);
|
color: var(--ink);
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stApp {
|
.stApp {
|
||||||
background: linear-gradient(135deg, #0a1628 0%, #0d1f3c 50%, #091520 100%);
|
background: var(--cream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
[data-testid="stSidebar"] {
|
[data-testid="stSidebar"] {
|
||||||
background: linear-gradient(180deg, #0f2040 0%, #0a1628 100%);
|
background: #f5f2eb;
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
[data-testid="stSidebar"] .stSlider label,
|
[data-testid="stSidebar"] .stSlider label,
|
||||||
[data-testid="stSidebar"] .stNumberInput label,
|
[data-testid="stSidebar"] .stNumberInput label,
|
||||||
[data-testid="stSidebar"] .stSelectbox label {
|
[data-testid="stSidebar"] .stSelectbox label {
|
||||||
color: var(--accent-green) !important;
|
color: var(--soil) !important;
|
||||||
font-size: 0.82rem;
|
font-size: 0.85rem;
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-weight: 500;
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Metric cards */
|
/* Metric cards */
|
||||||
.metric-card {
|
.metric-card {
|
||||||
background: linear-gradient(135deg, #0f2040, #132b55);
|
background: var(--paper);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
padding: 20px 24px;
|
padding: 20px 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
box-shadow: 0 2px 10px var(--shadow);
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.metric-card::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0; left: 0; right: 0;
|
|
||||||
height: 2px;
|
|
||||||
background: linear-gradient(90deg, var(--accent-green), var(--accent-blue));
|
|
||||||
}
|
}
|
||||||
.metric-value {
|
.metric-value {
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-size: 1.9rem;
|
||||||
font-size: 2.2rem;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--accent-green);
|
color: var(--leaf);
|
||||||
line-height: 1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
.metric-unit {
|
.metric-unit {
|
||||||
font-size: 0.85rem;
|
font-size: 0.8rem;
|
||||||
color: var(--text-muted);
|
color: var(--ink-muted);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
.metric-label {
|
.metric-label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--text-primary);
|
color: var(--ink);
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section headers */
|
/* Section headers */
|
||||||
.section-header {
|
.section-header {
|
||||||
font-size: 0.75rem;
|
font-size: 0.95rem;
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-weight: 600;
|
||||||
letter-spacing: 0.15em;
|
color: var(--soil);
|
||||||
color: var(--accent-gold);
|
padding-bottom: 8px;
|
||||||
text-transform: uppercase;
|
margin-bottom: 14px;
|
||||||
border-bottom: 1px solid rgba(245,158,11,0.3);
|
margin-top: 22px;
|
||||||
padding-bottom: 6px;
|
border-bottom: 1px solid var(--border);
|
||||||
margin-bottom: 16px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Crop badge */
|
/* Crop badge */
|
||||||
.crop-badge {
|
.crop-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: linear-gradient(135deg, rgba(74,222,128,0.15), rgba(56,189,248,0.15));
|
background: #f3f6f3;
|
||||||
border: 1px solid var(--accent-green);
|
border: 1px solid var(--leaf-light);
|
||||||
border-radius: 6px;
|
border-radius: 999px;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-size: 0.82rem;
|
||||||
font-size: 0.78rem;
|
color: var(--leaf);
|
||||||
color: var(--accent-green);
|
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Recommendation card */
|
/* Recommendation card */
|
||||||
.rec-card {
|
.rec-card {
|
||||||
background: linear-gradient(135deg, rgba(74,222,128,0.08), rgba(56,189,248,0.05));
|
background: var(--paper);
|
||||||
border: 1px solid rgba(74,222,128,0.3);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 18px 22px;
|
padding: 14px 18px;
|
||||||
margin: 10px 0;
|
margin: 8px 0;
|
||||||
|
box-shadow: 0 1px 6px var(--shadow);
|
||||||
}
|
}
|
||||||
.rec-rank {
|
.rec-rank {
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-size: 1.2rem;
|
||||||
font-size: 1.5rem;
|
color: var(--wheat);
|
||||||
color: var(--accent-gold);
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.rec-crop {
|
.rec-crop {
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
color: var(--text-primary);
|
color: var(--ink);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.rec-score {
|
.rec-score {
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--accent-blue);
|
color: var(--leaf);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hero title */
|
/* Hero title */
|
||||||
.hero-title {
|
.hero-title {
|
||||||
font-size: 2rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: linear-gradient(135deg, var(--accent-green), var(--accent-blue));
|
color: var(--soil);
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
.hero-sub {
|
.hero-sub {
|
||||||
font-family: "JetBrains Mono", "Fira Code", "Source Code Pro", monospace;
|
font-size: 0.85rem;
|
||||||
font-size: 0.8rem;
|
color: var(--ink-muted);
|
||||||
color: var(--text-muted);
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alert boxes */
|
/* Alert boxes */
|
||||||
.alert-good {
|
.alert-good {
|
||||||
background: rgba(74,222,128,0.1);
|
background: #f4faf5;
|
||||||
border-left: 3px solid var(--accent-green);
|
border-left: 3px solid var(--leaf-light);
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
padding: 12px 16px;
|
padding: 12px 14px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.alert-warn {
|
.alert-warn {
|
||||||
background: rgba(245,158,11,0.1);
|
background: #fdf9f3;
|
||||||
border-left: 3px solid var(--accent-gold);
|
border-left: 3px solid var(--wheat);
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
padding: 12px 16px;
|
padding: 12px 14px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override streamlit slider colors */
|
/* Sidebar title */
|
||||||
|
.sidebar-title {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--soil);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.sidebar-sub {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Streamlit overrides */
|
||||||
.stSlider [data-baseweb="slider"] [data-testid="stTickBarMin"],
|
.stSlider [data-baseweb="slider"] [data-testid="stTickBarMin"],
|
||||||
.stSlider [data-baseweb="slider"] [data-testid="stTickBarMax"] {
|
.stSlider [data-baseweb="slider"] [data-testid="stTickBarMax"] {
|
||||||
color: var(--text-muted);
|
color: var(--ink-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stButton > button {
|
||||||
|
border-radius: 10px !important;
|
||||||
|
background: var(--leaf) !important;
|
||||||
|
border: none !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.stButton > button:hover {
|
||||||
|
background: var(--leaf-light) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
@@ -194,42 +200,42 @@ CROPS = {
|
|||||||
"optimal": {"ph": (6.0, 7.0), "N": (80, 120), "P": (30, 60), "K": (40, 80),
|
"optimal": {"ph": (6.0, 7.0), "N": (80, 120), "P": (30, 60), "K": (40, 80),
|
||||||
"rainfall": (150, 250), "temp": (22, 30)},
|
"rainfall": (150, 250), "temp": (22, 30)},
|
||||||
"base_yield": 7500, # kg/ha
|
"base_yield": 7500, # kg/ha
|
||||||
"color": "#4ade80"
|
"color": "#4a7c59"
|
||||||
},
|
},
|
||||||
"小麦": {
|
"小麦": {
|
||||||
"emoji": "🌿",
|
"emoji": "🌿",
|
||||||
"optimal": {"ph": (6.0, 7.5), "N": (60, 100), "P": (20, 50), "K": (30, 60),
|
"optimal": {"ph": (6.0, 7.5), "N": (60, 100), "P": (20, 50), "K": (30, 60),
|
||||||
"rainfall": (60, 120), "temp": (15, 22)},
|
"rainfall": (60, 120), "temp": (15, 22)},
|
||||||
"base_yield": 6000,
|
"base_yield": 6000,
|
||||||
"color": "#f59e0b"
|
"color": "#c69c5d"
|
||||||
},
|
},
|
||||||
"玉米": {
|
"玉米": {
|
||||||
"emoji": "🌽",
|
"emoji": "🌽",
|
||||||
"optimal": {"ph": (5.8, 7.0), "N": (100, 150), "P": (40, 70), "K": (60, 100),
|
"optimal": {"ph": (5.8, 7.0), "N": (100, 150), "P": (40, 70), "K": (60, 100),
|
||||||
"rainfall": (100, 180), "temp": (20, 28)},
|
"rainfall": (100, 180), "temp": (20, 28)},
|
||||||
"base_yield": 8500,
|
"base_yield": 8500,
|
||||||
"color": "#fbbf24"
|
"color": "#e8a93f"
|
||||||
},
|
},
|
||||||
"大豆": {
|
"大豆": {
|
||||||
"emoji": "🫘",
|
"emoji": "🫘",
|
||||||
"optimal": {"ph": (6.0, 7.0), "N": (20, 50), "P": (30, 60), "K": (40, 80),
|
"optimal": {"ph": (6.0, 7.0), "N": (20, 50), "P": (30, 60), "K": (40, 80),
|
||||||
"rainfall": (80, 150), "temp": (18, 26)},
|
"rainfall": (80, 150), "temp": (18, 26)},
|
||||||
"base_yield": 3500,
|
"base_yield": 3500,
|
||||||
"color": "#a78bfa"
|
"color": "#8b7cb3"
|
||||||
},
|
},
|
||||||
"油菜": {
|
"油菜": {
|
||||||
"emoji": "🌻",
|
"emoji": "🌻",
|
||||||
"optimal": {"ph": (6.0, 7.5), "N": (80, 130), "P": (30, 60), "K": (50, 90),
|
"optimal": {"ph": (6.0, 7.5), "N": (80, 130), "P": (30, 60), "K": (50, 90),
|
||||||
"rainfall": (80, 130), "temp": (15, 20)},
|
"rainfall": (80, 130), "temp": (15, 20)},
|
||||||
"base_yield": 3000,
|
"base_yield": 3000,
|
||||||
"color": "#f97316"
|
"color": "#d97836"
|
||||||
},
|
},
|
||||||
"棉花": {
|
"棉花": {
|
||||||
"emoji": "☁️",
|
"emoji": "☁️",
|
||||||
"optimal": {"ph": (6.0, 8.0), "N": (60, 100), "P": (20, 45), "K": (40, 70),
|
"optimal": {"ph": (6.0, 8.0), "N": (60, 100), "P": (20, 45), "K": (40, 70),
|
||||||
"rainfall": (70, 120), "temp": (25, 32)},
|
"rainfall": (70, 120), "temp": (25, 32)},
|
||||||
"base_yield": 4500,
|
"base_yield": 4500,
|
||||||
"color": "#e2e8f0"
|
"color": "#5a6b7c"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,11 +300,11 @@ def rank_crops(ph, N, P, K, rainfall, temp, pesticide, area):
|
|||||||
|
|
||||||
# ─── Sidebar Inputs ──────────────────────────────────────────────────────────
|
# ─── Sidebar Inputs ──────────────────────────────────────────────────────────
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
st.markdown('<div class="hero-title">🌾 农业决策</div>', unsafe_allow_html=True)
|
st.markdown('<div class="sidebar-title">🌾 种植决策助手</div>', unsafe_allow_html=True)
|
||||||
st.markdown('<div class="hero-sub">SMART FARMING SYSTEM v2.0</div>', unsafe_allow_html=True)
|
st.markdown('<div class="sidebar-sub">根据土壤和气候,推荐适宜作物</div>', unsafe_allow_html=True)
|
||||||
st.markdown("---")
|
st.markdown("<hr style='border:none;border-top:1px solid var(--border);margin:12px 0;'>", unsafe_allow_html=True)
|
||||||
|
|
||||||
st.markdown('<div class="section-header">🧪 土壤参数</div>', unsafe_allow_html=True)
|
st.markdown('<div class="section-header" style="margin-top:0">🧪 土壤参数</div>', unsafe_allow_html=True)
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
with col1:
|
with col1:
|
||||||
ph = st.slider("pH 值", 4.0, 9.0, 6.5, 0.1)
|
ph = st.slider("pH 值", 4.0, 9.0, 6.5, 0.1)
|
||||||
@@ -334,10 +340,10 @@ best_crop = rankings[0]
|
|||||||
|
|
||||||
# ─── Main Layout ─────────────────────────────────────────────────────────────
|
# ─── Main Layout ─────────────────────────────────────────────────────────────
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div style="display:flex; align-items:baseline; gap:16px; margin-bottom:4px;">
|
<div style="display:flex; align-items:baseline; gap:12px; margin-bottom:4px;">
|
||||||
<div class="hero-title">农业智能决策系统</div>
|
<div class="hero-title">种植决策助手</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-sub">YIELD = f(SOIL · WEATHER · PESTICIDE) | 基于多因子 Cobb-Douglas 产量模型</div>
|
<div class="hero-sub">输入土壤与气象条件,获得作物产量预测与种植建议</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
st.markdown("<br>", unsafe_allow_html=True)
|
st.markdown("<br>", unsafe_allow_html=True)
|
||||||
@@ -392,14 +398,14 @@ with col_left:
|
|||||||
r=factor_vals_closed,
|
r=factor_vals_closed,
|
||||||
theta=factor_names_closed,
|
theta=factor_names_closed,
|
||||||
fill='toself',
|
fill='toself',
|
||||||
fillcolor='rgba(74,222,128,0.15)',
|
fillcolor='rgba(74, 124, 89, 0.15)',
|
||||||
line=dict(color='#4ade80', width=2),
|
line=dict(color='#4a7c59', width=2),
|
||||||
name=selected_crop,
|
name=selected_crop,
|
||||||
))
|
))
|
||||||
fig_radar.add_trace(go.Scatterpolar(
|
fig_radar.add_trace(go.Scatterpolar(
|
||||||
r=[100]*len(factor_names_closed),
|
r=[100]*len(factor_names_closed),
|
||||||
theta=factor_names_closed,
|
theta=factor_names_closed,
|
||||||
line=dict(color='rgba(255,255,255,0.1)', width=1, dash='dot'),
|
line=dict(color='rgba(0,0,0,0.15)', width=1, dash='dot'),
|
||||||
mode='lines',
|
mode='lines',
|
||||||
name='理想值',
|
name='理想值',
|
||||||
))
|
))
|
||||||
@@ -407,14 +413,14 @@ with col_left:
|
|||||||
polar=dict(
|
polar=dict(
|
||||||
bgcolor='rgba(0,0,0,0)',
|
bgcolor='rgba(0,0,0,0)',
|
||||||
radialaxis=dict(range=[0, 100], showticklabels=True,
|
radialaxis=dict(range=[0, 100], showticklabels=True,
|
||||||
tickfont=dict(color='#64748b', size=9),
|
tickfont=dict(color='#5a5a5a', size=9),
|
||||||
gridcolor='rgba(255,255,255,0.06)'),
|
gridcolor='rgba(0,0,0,0.08)'),
|
||||||
angularaxis=dict(tickfont=dict(color='#e2e8f0', size=11),
|
angularaxis=dict(tickfont=dict(color='#2c2c2c', size=11),
|
||||||
gridcolor='rgba(255,255,255,0.08)'),
|
gridcolor='rgba(0,0,0,0.1)'),
|
||||||
),
|
),
|
||||||
paper_bgcolor='rgba(0,0,0,0)',
|
paper_bgcolor='rgba(0,0,0,0)',
|
||||||
plot_bgcolor='rgba(0,0,0,0)',
|
plot_bgcolor='rgba(0,0,0,0)',
|
||||||
font=dict(color='#e2e8f0'),
|
font=dict(color='#2c2c2c'),
|
||||||
legend=dict(orientation='h', y=-0.12, font=dict(size=10)),
|
legend=dict(orientation='h', y=-0.12, font=dict(size=10)),
|
||||||
margin=dict(t=20, b=40, l=40, r=40),
|
margin=dict(t=20, b=40, l=40, r=40),
|
||||||
height=320,
|
height=320,
|
||||||
@@ -436,11 +442,11 @@ with col_right:
|
|||||||
</div>
|
</div>
|
||||||
<span class="rec-score">{r['score']*100:.1f}%</span>
|
<span class="rec-score">{r['score']*100:.1f}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="background:rgba(255,255,255,0.06); border-radius:4px; height:4px; margin-top:8px; overflow:hidden;">
|
<div style="background:#f0ece4; border-radius:4px; height:5px; margin-top:10px; overflow:hidden;">
|
||||||
<div style="width:{bar_width}%; height:100%; background:{bar_color}; border-radius:4px;"></div>
|
<div style="width:{bar_width}%; height:100%; background:{bar_color}; border-radius:4px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:0.78rem; color:#64748b; margin-top:4px; font-family:'JetBrains Mono',monospace;">
|
<div style="font-size:0.78rem; color:#7a7a7a; margin-top:6px;">
|
||||||
{r['yield_ha']:,.0f} kg/ha · 总产 {r['total_yield']/1000:,.1f} 吨
|
{r['yield_ha']:,.0f} kg/ha · 总产 {r['total_yield']/1000:,.1f} 吨
|
||||||
</div>
|
</div>
|
||||||
</div>""", unsafe_allow_html=True)
|
</div>""", unsafe_allow_html=True)
|
||||||
|
|
||||||
@@ -456,18 +462,18 @@ with sa_col1:
|
|||||||
fig_N = go.Figure()
|
fig_N = go.Figure()
|
||||||
fig_N.add_trace(go.Scatter(
|
fig_N.add_trace(go.Scatter(
|
||||||
x=N_range, y=y_N,
|
x=N_range, y=y_N,
|
||||||
mode='lines', line=dict(color='#4ade80', width=2.5),
|
mode='lines', line=dict(color='#4a7c59', width=2.5),
|
||||||
fill='tozeroy', fillcolor='rgba(74,222,128,0.08)',
|
fill='tozeroy', fillcolor='rgba(74, 124, 89, 0.08)',
|
||||||
name='产量'
|
name='产量'
|
||||||
))
|
))
|
||||||
fig_N.add_vline(x=N, line=dict(color='#f59e0b', width=1.5, dash='dot'),
|
fig_N.add_vline(x=N, line=dict(color='#d4a574', width=1.5, dash='dot'),
|
||||||
annotation_text=f"当前 {N}", annotation_font_color='#f59e0b')
|
annotation_text=f"当前 {N}", annotation_font_color='#7c5e42')
|
||||||
fig_N.update_layout(
|
fig_N.update_layout(
|
||||||
title=dict(text="氮肥用量 vs 产量", font=dict(color='#94a3b8', size=12)),
|
title=dict(text="氮肥用量 vs 产量", font=dict(color='#5a5a5a', size=12)),
|
||||||
xaxis=dict(title="氮 N (mg/kg)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'),
|
xaxis=dict(title="氮 N (mg/kg)", color='#5a5a5a', gridcolor='rgba(0,0,0,0.06)'),
|
||||||
yaxis=dict(title="产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'),
|
yaxis=dict(title="产量 (kg/ha)", color='#5a5a5a', gridcolor='rgba(0,0,0,0.06)'),
|
||||||
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
||||||
font=dict(color='#e2e8f0', size=10),
|
font=dict(color='#2c2c2c', size=10),
|
||||||
margin=dict(t=36, b=36, l=50, r=20), height=220,
|
margin=dict(t=36, b=36, l=50, r=20), height=220,
|
||||||
showlegend=False,
|
showlegend=False,
|
||||||
)
|
)
|
||||||
@@ -480,18 +486,18 @@ with sa_col2:
|
|||||||
fig_R = go.Figure()
|
fig_R = go.Figure()
|
||||||
fig_R.add_trace(go.Scatter(
|
fig_R.add_trace(go.Scatter(
|
||||||
x=rain_range, y=y_rain,
|
x=rain_range, y=y_rain,
|
||||||
mode='lines', line=dict(color='#38bdf8', width=2.5),
|
mode='lines', line=dict(color='#5a8f9e', width=2.5),
|
||||||
fill='tozeroy', fillcolor='rgba(56,189,248,0.08)',
|
fill='tozeroy', fillcolor='rgba(90, 143, 158, 0.08)',
|
||||||
name='产量'
|
name='产量'
|
||||||
))
|
))
|
||||||
fig_R.add_vline(x=rainfall, line=dict(color='#f59e0b', width=1.5, dash='dot'),
|
fig_R.add_vline(x=rainfall, line=dict(color='#d4a574', width=1.5, dash='dot'),
|
||||||
annotation_text=f"当前 {rainfall}mm", annotation_font_color='#f59e0b')
|
annotation_text=f"当前 {rainfall}mm", annotation_font_color='#7c5e42')
|
||||||
fig_R.update_layout(
|
fig_R.update_layout(
|
||||||
title=dict(text="月降雨量 vs 产量", font=dict(color='#94a3b8', size=12)),
|
title=dict(text="月降雨量 vs 产量", font=dict(color='#5a5a5a', size=12)),
|
||||||
xaxis=dict(title="降雨量 (mm/月)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'),
|
xaxis=dict(title="降雨量 (mm/月)", color='#5a5a5a', gridcolor='rgba(0,0,0,0.06)'),
|
||||||
yaxis=dict(title="产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'),
|
yaxis=dict(title="产量 (kg/ha)", color='#5a5a5a', gridcolor='rgba(0,0,0,0.06)'),
|
||||||
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
||||||
font=dict(color='#e2e8f0', size=10),
|
font=dict(color='#2c2c2c', size=10),
|
||||||
margin=dict(t=36, b=36, l=50, r=20), height=220,
|
margin=dict(t=36, b=36, l=50, r=20), height=220,
|
||||||
showlegend=False,
|
showlegend=False,
|
||||||
)
|
)
|
||||||
@@ -508,23 +514,23 @@ fig_bar = go.Figure()
|
|||||||
fig_bar.add_trace(go.Bar(
|
fig_bar.add_trace(go.Bar(
|
||||||
x=crop_names, y=crop_yields,
|
x=crop_names, y=crop_yields,
|
||||||
marker=dict(color=crop_colors, opacity=0.85,
|
marker=dict(color=crop_colors, opacity=0.85,
|
||||||
line=dict(color='rgba(255,255,255,0.2)', width=1)),
|
line=dict(color='rgba(0,0,0,0.08)', width=1)),
|
||||||
text=[f"{y:,.0f}" for y in crop_yields],
|
text=[f"{y:,.0f}" for y in crop_yields],
|
||||||
textposition='outside',
|
textposition='outside',
|
||||||
textfont=dict(color='#94a3b8', size=10, family='JetBrains Mono'),
|
textfont=dict(color='#5a5a5a', size=10),
|
||||||
))
|
))
|
||||||
fig_bar.update_layout(
|
fig_bar.update_layout(
|
||||||
xaxis=dict(color='#64748b', gridcolor='rgba(255,255,255,0.04)'),
|
xaxis=dict(color='#5a5a5a', gridcolor='rgba(0,0,0,0.04)'),
|
||||||
yaxis=dict(title="预期产量 (kg/ha)", color='#64748b', gridcolor='rgba(255,255,255,0.05)'),
|
yaxis=dict(title="预期产量 (kg/ha)", color='#5a5a5a', gridcolor='rgba(0,0,0,0.06)'),
|
||||||
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)',
|
||||||
font=dict(color='#e2e8f0', size=11),
|
font=dict(color='#2c2c2c', size=11),
|
||||||
margin=dict(t=20, b=30, l=60, r=20), height=240,
|
margin=dict(t=20, b=30, l=60, r=20), height=260,
|
||||||
showlegend=False,
|
showlegend=False,
|
||||||
)
|
)
|
||||||
st.plotly_chart(fig_bar, use_container_width=True)
|
st.plotly_chart(fig_bar, use_container_width=True)
|
||||||
|
|
||||||
# ─── Advisory Panel ───────────────────────────────────────────────────────────
|
# ─── Advisory Panel ───────────────────────────────────────────────────────────
|
||||||
st.markdown('<div class="section-header">💡 智能建议</div>', unsafe_allow_html=True)
|
st.markdown('<div class="section-header">💡 种植建议</div>', unsafe_allow_html=True)
|
||||||
adv1, adv2 = st.columns(2)
|
adv1, adv2 = st.columns(2)
|
||||||
|
|
||||||
with adv1:
|
with adv1:
|
||||||
@@ -557,7 +563,7 @@ with adv1:
|
|||||||
with adv2:
|
with adv2:
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div class="rec-card">
|
<div class="rec-card">
|
||||||
<div style="font-size:0.82rem; color:#64748b; font-family:'JetBrains Mono',monospace; margin-bottom:12px;">
|
<div style="font-size:0.82rem; color:#7a7a7a; margin-bottom:12px;">
|
||||||
当前环境参数下适宜种植:
|
当前环境参数下适宜种植:
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
@@ -568,18 +574,17 @@ with adv2:
|
|||||||
st.markdown(f'{badges}</div>', unsafe_allow_html=True)
|
st.markdown(f'{badges}</div>', unsafe_allow_html=True)
|
||||||
|
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div style="margin-top:16px; font-size:0.85rem; color:#94a3b8; line-height:1.7;">
|
<div style="margin-top:16px; font-size:0.88rem; color:#5a5a5a; line-height:1.7; background:#fff; border:1px solid #e5e0d5; border-radius:12px; padding:14px 16px;">
|
||||||
<b style="color:#4ade80;">最优方案:</b>{best_crop['emoji']} {best_crop['crop']}<br>
|
<b style="color:#4a7c59;">最优方案:</b>{best_crop['emoji']} {best_crop['crop']}<br>
|
||||||
预期单产:<span style="font-family:'JetBrains Mono',monospace; color:#38bdf8;">{best_crop['yield_ha']:,.0f} kg/ha</span><br>
|
预期单产:<span style="color:#4a7c59; font-weight:600;">{best_crop['yield_ha']:,.0f} kg/ha</span><br>
|
||||||
{area:.0f}公顷总产:<span style="font-family:'JetBrains Mono',monospace; color:#38bdf8;">{best_crop['total_yield']/1000:,.1f} 吨</span>
|
{area:.0f}公顷总产:<span style="color:#4a7c59; font-weight:600;">{best_crop['total_yield']/1000:,.1f} 吨</span>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
# ─── Footer ───────────────────────────────────────────────────────────────────
|
# ─── Footer ───────────────────────────────────────────────────────────────────
|
||||||
st.markdown("<br>", unsafe_allow_html=True)
|
st.markdown("<br>", unsafe_allow_html=True)
|
||||||
st.markdown("""
|
st.markdown("""
|
||||||
<div style="text-align:center; font-family:'JetBrains Mono',monospace; font-size:0.72rem;
|
<div style="text-align:center; font-size:0.78rem; color:#aaa; padding:14px; border-top:1px solid #e5e0d5;">
|
||||||
color:#334155; padding:16px; border-top:1px solid rgba(74,222,128,0.1);">
|
种植决策助手 · 基于 Cobb-Douglas 多因子产量模型 · 仅供参考
|
||||||
YIELD = f(Soil, Weather, Pesticide) | Cobb-Douglas Multi-Factor Model | 农业智能决策系统
|
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user