feat: 优化GUI推荐方案选择和用户交互
This commit is contained in:
106
gui.py
106
gui.py
@@ -2,6 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import io
|
import io
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import tempfile
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from nicegui import ui, events
|
from nicegui import ui, events
|
||||||
from main import (
|
from main import (
|
||||||
@@ -35,7 +36,7 @@ state = {
|
|||||||
"results": [],
|
"results": [],
|
||||||
"substation": None,
|
"substation": None,
|
||||||
"turbines": None,
|
"turbines": None,
|
||||||
"temp_dir": ".gemini/tmp/gui_uploads",
|
"temp_dir": os.path.join(tempfile.gettempdir(), "windfarm_gui_uploads"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 确保临时目录存在
|
# 确保临时目录存在
|
||||||
@@ -45,6 +46,13 @@ if not os.path.exists(state["temp_dir"]):
|
|||||||
|
|
||||||
@ui.page("/")
|
@ui.page("/")
|
||||||
def index():
|
def index():
|
||||||
|
# 注入 CSS 隐藏表格自带的复选框列,并定义选中行的高亮背景色
|
||||||
|
ui.add_head_html("""
|
||||||
|
<style>
|
||||||
|
.hide-selection-column .q-table__selection { display: none; }
|
||||||
|
.hide-selection-column .q-table tr.selected { background-color: #e3f2fd !important; }
|
||||||
|
</style>
|
||||||
|
""")
|
||||||
ui.query("body").style(
|
ui.query("body").style(
|
||||||
'background-color: #f0f2f5; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;'
|
'background-color: #f0f2f5; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;'
|
||||||
)
|
)
|
||||||
@@ -57,7 +65,6 @@ def index():
|
|||||||
"export_row": None,
|
"export_row": None,
|
||||||
"status_label": None,
|
"status_label": None,
|
||||||
"upload_widget": None,
|
"upload_widget": None,
|
||||||
"clusters_input": None,
|
|
||||||
"run_btn": None,
|
"run_btn": None,
|
||||||
"current_file_label": None,
|
"current_file_label": None,
|
||||||
}
|
}
|
||||||
@@ -170,29 +177,36 @@ def index():
|
|||||||
ax=ax,
|
ax=ax,
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_row_click(e):
|
async def handle_row_click(e):
|
||||||
# ui.table row-click args: [evt, row, index]
|
# 获取被点击行的数据
|
||||||
row = None
|
row = e.args[1] if len(e.args) > 1 else None
|
||||||
if e.args and isinstance(e.args, list) and len(e.args) > 1:
|
if not row:
|
||||||
row = e.args[1]
|
return
|
||||||
|
|
||||||
if not row or "name" not in row:
|
# 识别方案名称
|
||||||
|
row_name = row.get("original_name", row.get("name"))
|
||||||
|
if not row_name:
|
||||||
return
|
return
|
||||||
|
|
||||||
row_name = row["name"]
|
|
||||||
selected_res = next(
|
selected_res = next(
|
||||||
(r for r in state["results"] if r["name"] == row_name), None
|
(r for r in state["results"] if r["name"] == row_name), None
|
||||||
)
|
)
|
||||||
if selected_res:
|
if selected_res:
|
||||||
|
# 1. 更新拓扑图
|
||||||
update_plot(selected_res)
|
update_plot(selected_res)
|
||||||
ui.notify(f"已切换至方案: {row_name}")
|
ui.notify(f"已切换至方案: {selected_res['name']}")
|
||||||
|
|
||||||
|
# 2. 通过设置 table 的 selected 属性来实现背景高亮 (监听点击事件驱动)
|
||||||
|
if refs["results_table"]:
|
||||||
|
refs["results_table"].selected = [row]
|
||||||
|
|
||||||
from nicegui import run
|
from nicegui import run
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
async def run_analysis(n_clusters):
|
async def run_analysis():
|
||||||
if not state["excel_path"]:
|
if not state["excel_path"]:
|
||||||
ui.notify("请先上传 Excel 坐标文件!", type="warning")
|
ui.notify("请先上传 Excel 坐标文件!", type="warning")
|
||||||
|
return
|
||||||
if refs["log_box"]:
|
if refs["log_box"]:
|
||||||
refs["log_box"].clear()
|
refs["log_box"].clear()
|
||||||
log_queue = queue.Queue()
|
log_queue = queue.Queue()
|
||||||
@@ -233,7 +247,7 @@ def index():
|
|||||||
with contextlib.redirect_stdout(QueueLogger()):
|
with contextlib.redirect_stdout(QueueLogger()):
|
||||||
return compare_design_methods(
|
return compare_design_methods(
|
||||||
excel_path=state["excel_path"],
|
excel_path=state["excel_path"],
|
||||||
n_clusters_override=n_clusters,
|
n_clusters_override=None,
|
||||||
interactive=False,
|
interactive=False,
|
||||||
plot_results=False,
|
plot_results=False,
|
||||||
)
|
)
|
||||||
@@ -248,28 +262,48 @@ def index():
|
|||||||
n_turbines=30, layout="grid", spacing=800
|
n_turbines=30, layout="grid", spacing=800
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新结果表格
|
|
||||||
if refs["results_table"]:
|
|
||||||
table_data = []
|
|
||||||
for res in results:
|
|
||||||
table_data.append(
|
|
||||||
{
|
|
||||||
"name": res["name"],
|
|
||||||
"cost_wan": round(res["cost"] / 10000, 2),
|
|
||||||
"loss_kw": round(res["loss"], 2),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
refs["results_table"].rows = table_data
|
|
||||||
refs["results_table"].update()
|
|
||||||
|
|
||||||
# 计算完成后,自动寻找并显示最佳方案的拓扑图
|
# 计算完成后,自动寻找并显示最佳方案的拓扑图
|
||||||
|
best_res = None
|
||||||
if results:
|
if results:
|
||||||
|
# 默认推荐 Scenario 1 中成本最低的方案
|
||||||
|
scenario1_results = [r for r in results if "Scenario 1" in r["name"]]
|
||||||
|
if scenario1_results:
|
||||||
|
best_res = min(scenario1_results, key=lambda x: x["cost"])
|
||||||
|
else:
|
||||||
|
# 如果没有 Scenario 1,则回退到全局最优
|
||||||
best_res = min(results, key=lambda x: x["cost"])
|
best_res = min(results, key=lambda x: x["cost"])
|
||||||
|
|
||||||
update_plot(best_res)
|
update_plot(best_res)
|
||||||
ui.notify(
|
ui.notify(
|
||||||
f'计算完成!已自动加载推荐方案: {best_res["name"]}', type="positive"
|
f'计算完成!已自动加载推荐方案: {best_res["name"]}', type="positive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 更新结果表格
|
||||||
|
if refs["results_table"]:
|
||||||
|
table_data = []
|
||||||
|
best_row = None
|
||||||
|
for res in results:
|
||||||
|
name_display = res["name"]
|
||||||
|
is_best = False
|
||||||
|
if best_res and res["name"] == best_res["name"]:
|
||||||
|
name_display = f"(推荐) {name_display}"
|
||||||
|
is_best = True
|
||||||
|
|
||||||
|
row_dict = {
|
||||||
|
"name": name_display,
|
||||||
|
"cost_wan": round(res["cost"] / 10000, 2),
|
||||||
|
"loss_kw": round(res["loss"], 2),
|
||||||
|
"original_name": res["name"],
|
||||||
|
}
|
||||||
|
table_data.append(row_dict)
|
||||||
|
if is_best:
|
||||||
|
best_row = row_dict
|
||||||
|
|
||||||
|
refs["results_table"].rows = table_data
|
||||||
|
# 初始选中推荐方案,实现自动高亮
|
||||||
|
if best_row:
|
||||||
|
refs["results_table"].selected = [best_row]
|
||||||
|
|
||||||
update_export_buttons()
|
update_export_buttons()
|
||||||
if refs["status_label"]:
|
if refs["status_label"]:
|
||||||
refs["status_label"].text = "计算完成!"
|
refs["status_label"].text = "计算完成!"
|
||||||
@@ -313,14 +347,10 @@ def index():
|
|||||||
).classes("w-full mb-2")
|
).classes("w-full mb-2")
|
||||||
# refs['current_file_label'] = ui.label('未选择文件').classes('text-xs text-gray-500 mb-4 italic')
|
# refs['current_file_label'] = ui.label('未选择文件').classes('text-xs text-gray-500 mb-4 italic')
|
||||||
|
|
||||||
ui.label("2. 参数设置").classes("font-medium mt-4")
|
|
||||||
refs["clusters_input"] = ui.number(
|
|
||||||
"指定回路数 (可选)", value=None, format="%d", placeholder="自动计算"
|
|
||||||
).classes("w-full mb-4")
|
|
||||||
refs["run_btn"] = (
|
refs["run_btn"] = (
|
||||||
ui.button(
|
ui.button(
|
||||||
"运行方案对比",
|
"运行方案对比",
|
||||||
on_click=lambda: run_analysis(refs["clusters_input"].value),
|
on_click=run_analysis,
|
||||||
)
|
)
|
||||||
.classes("w-full mt-4 py-4")
|
.classes("w-full mt-4 py-4")
|
||||||
.props("icon=play_arrow color=secondary")
|
.props("icon=play_arrow color=secondary")
|
||||||
@@ -352,10 +382,14 @@ def index():
|
|||||||
"sortable": True,
|
"sortable": True,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
# 移除 selection='single',改为纯行点击交互
|
# 使用内置的 selection='single' 结合行点击事件实现背景高亮
|
||||||
refs["results_table"] = ui.table(columns=columns, rows=[]).classes(
|
# 这样可以完全由 Python 事件逻辑控制,不依赖 CSS 伪类
|
||||||
"w-full"
|
refs["results_table"] = ui.table(
|
||||||
)
|
columns=columns,
|
||||||
|
rows=[],
|
||||||
|
selection="single",
|
||||||
|
row_key="original_name",
|
||||||
|
).classes("w-full hide-selection-column")
|
||||||
refs["results_table"].on("row-click", handle_row_click)
|
refs["results_table"].on("row-click", handle_row_click)
|
||||||
with ui.card().classes("w-full p-4 shadow-md"):
|
with ui.card().classes("w-full p-4 shadow-md"):
|
||||||
ui.label("拓扑可视化").classes("text-xl font-semibold mb-2")
|
ui.label("拓扑可视化").classes("text-xl font-semibold mb-2")
|
||||||
|
|||||||
Reference in New Issue
Block a user