From 3f73a9be266231b6407ee50fa3164ecc181f9d08 Mon Sep 17 00:00:00 2001 From: dmy Date: Sun, 4 Jan 2026 18:33:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E7=AB=AF=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gui.py | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 9 deletions(-) diff --git a/gui.py b/gui.py index 1a475d7..534b75b 100644 --- a/gui.py +++ b/gui.py @@ -33,6 +33,7 @@ class Logger(io.StringIO): # 状态变量 state = { "excel_path": None, + "original_filename": None, "results": [], "substation": None, "turbines": None, @@ -63,6 +64,7 @@ def index(): "results_table": None, "plot_container": None, "export_row": None, + "export_selected_btn": None, # 新增按钮引用 "status_label": None, "upload_widget": None, "run_btn": None, @@ -116,6 +118,7 @@ def index(): f.write(data) state["excel_path"] = path + state["original_filename"] = filename ui.notify(f"文件已上传: {filename}", type="positive") if refs["current_file_label"]: refs["current_file_label"].text = f"当前文件: {filename}" @@ -130,20 +133,38 @@ def index(): refs["export_row"].clear() if not state["results"] or not refs["export_row"]: return + + # 获取带 _result 后缀的文件名 + download_name = "wind_farm_design_result.xlsx" + if state.get("original_filename"): + name_no_ext = os.path.splitext(state["original_filename"])[0] + download_name = f"{name_no_ext}_result.xlsx" + + # 寻找推荐方案:优先 Scenario 1 的最低成本,否则取全局最低成本 + scenario1_results = [r for r in state["results"] if "Scenario 1" in r["name"]] + if scenario1_results: + best_res = min(scenario1_results, key=lambda x: x["cost"]) + else: + best_res = min(state["results"], key=lambda x: x["cost"]) + with refs["export_row"]: ui.button( "下载 Excel 对比表", - on_click=lambda: ui.download("wind_farm_design.xlsx"), + on_click=lambda: ui.download("wind_farm_design.xlsx", download_name), ).props("icon=download") - best_idx = 0 - for i, res in enumerate(state["results"]): - if res["cost"] < state["results"][best_idx]["cost"]: - best_idx = i - best_res = state["results"][best_idx] def export_best_dxf(): - dxf_name = "best_design.dxf" if state["substation"] is not None: + # 生成安全的文件名 + safe_name = "".join( + [ + c + for c in best_res["name"] + if c.isalnum() or c in (" ", "-", "_") + ] + ).strip() + dxf_name = f"design_best_{safe_name}.dxf" + export_to_dxf( best_res["turbines"], state["substation"], @@ -159,6 +180,89 @@ def index(): f'导出推荐方案 DXF ({best_res["name"]})', on_click=export_best_dxf ).props("icon=architecture color=accent") + def export_selected_dxf(): + if not refs["results_table"] or not refs["results_table"].selected: + ui.notify("请先在上方表格中选择一个方案", type="warning") + return + + selected_row = refs["results_table"].selected[0] + row_name = selected_row.get("original_name", selected_row.get("name")) + + selected_res = next( + (r for r in state["results"] if r["name"] == row_name), None + ) + + if selected_res and state["substation"] is not None: + safe_name = "".join( + [ + c + for c in selected_res["name"] + if c.isalnum() or c in (" ", "-", "_") + ] + ).strip() + dxf_name = f"design_{safe_name}.dxf" + + export_to_dxf( + selected_res["turbines"], + state["substation"], + selected_res["eval"]["details"], + dxf_name, + ) + ui.download(dxf_name) + ui.notify(f'已导出选中方案: {selected_res["name"]}', type="positive") + else: + ui.notify("无法导出:未找到方案数据或缺少升压站信息", type="negative") + + # 记录此按钮引用,以便 handle_row_click 更新文字 + refs["export_selected_btn"] = ui.button( + "导出选中方案 DXF", on_click=export_selected_dxf + ).props("icon=architecture color=primary") + + # 初始化按钮文字 + clean_name = best_res["name"].replace("(推荐) ", "") + refs["export_selected_btn"].set_text(f"导出选中方案 ({clean_name})") + + def export_all_dxf(): + if not state["results"] or state["substation"] is None: + ui.notify("无方案数据可导出", type="warning") + return + + import zipfile + + zip_filename = "all_designs.zip" + try: + with zipfile.ZipFile( + zip_filename, "w", zipfile.ZIP_DEFLATED + ) as zipf: + for res in state["results"]: + safe_name = "".join( + [ + c + for c in res["name"] + if c.isalnum() or c in (" ", "-", "_") + ] + ).strip() + dxf_name = f"design_{safe_name}.dxf" + export_to_dxf( + res["turbines"], + state["substation"], + res["eval"]["details"], + dxf_name, + ) + zipf.write(dxf_name) + try: + os.remove(dxf_name) + except: + pass + ui.download(zip_filename) + ui.notify("已导出所有方案", type="positive") + except Exception as ex: + ui.notify(f"导出失败: {ex}", type="negative") + + ui.button("导出全部方案 DXF (ZIP)", on_click=export_all_dxf).props( + "icon=folder_zip color=secondary" + ) + def update_plot(result): if refs["plot_container"]: refs["plot_container"].clear() @@ -196,10 +300,15 @@ def index(): update_plot(selected_res) ui.notify(f"已切换至方案: {selected_res['name']}") - # 2. 通过设置 table 的 selected 属性来实现背景高亮 (监听点击事件驱动) + # 2. 通过设置 table 的 selected 属性来实现背景高亮 if refs["results_table"]: refs["results_table"].selected = [row] + # 3. 更新“导出选中方案”按钮的文本 + if refs["export_selected_btn"]: + clean_name = row_name.replace("(推荐) ", "") + refs["export_selected_btn"].set_text(f"导出选中方案 ({clean_name})") + from nicegui import run import queue @@ -341,6 +450,20 @@ def index(): with ui.row().classes("w-full p-4 gap-4"): with ui.card().classes("w-1/4 p-4 shadow-md"): ui.label("配置面板").classes("text-xl font-semibold mb-4 border-b pb-2") + + def export_template(): + from generate_template import create_template + try: + create_template() + ui.download("coordinates.xlsx") + ui.notify("Excel 模板导出成功", type="positive") + except Exception as ex: + ui.notify(f"模板导出失败: {ex}", type="negative") + + ui.button("导出 Excel 模板", on_click=export_template).classes( + "w-full mb-4" + ).props("icon=file_download outline color=primary") + ui.label("1. 上传坐标文件 (.xlsx)").classes("font-medium") refs["upload_widget"] = ui.upload( label="选择Excel文件", on_upload=handle_upload, auto_upload=True @@ -399,4 +522,20 @@ def index(): refs["export_row"] = ui.row().classes("gap-4") -ui.run(title="海上风电场集电线路优化", port=8080) +def find_available_port(start_port=8080, max_attempts=10): + """尝试寻找可用的端口""" + import socket + + for port in range(start_port, start_port + max_attempts): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + s.bind(("0.0.0.0", port)) + return port + except OSError: + continue + return start_port # 默认返回起始端口,让 ui.run 报错 + + +# 自动寻找可用端口,避免端口冲突 +target_port = find_available_port(8080) +ui.run(title="海上风电场集电线路优化", port=target_port)