feat: 优化GUI推荐方案选择和用户交互

This commit is contained in:
dmy
2026-01-04 17:39:09 +08:00
parent 00d480edbb
commit 369430aa67

108
gui.py
View File

@@ -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:
best_res = min(results, key=lambda x: x["cost"]) # 默认推荐 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"])
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")