feat: 增强导出功能和端口自动分配

This commit is contained in:
dmy
2026-01-04 18:33:34 +08:00
parent 369430aa67
commit 3f73a9be26

157
gui.py
View File

@@ -33,6 +33,7 @@ class Logger(io.StringIO):
# 状态变量 # 状态变量
state = { state = {
"excel_path": None, "excel_path": None,
"original_filename": None,
"results": [], "results": [],
"substation": None, "substation": None,
"turbines": None, "turbines": None,
@@ -63,6 +64,7 @@ def index():
"results_table": None, "results_table": None,
"plot_container": None, "plot_container": None,
"export_row": None, "export_row": None,
"export_selected_btn": None, # 新增按钮引用
"status_label": None, "status_label": None,
"upload_widget": None, "upload_widget": None,
"run_btn": None, "run_btn": None,
@@ -116,6 +118,7 @@ def index():
f.write(data) f.write(data)
state["excel_path"] = path state["excel_path"] = path
state["original_filename"] = filename
ui.notify(f"文件已上传: {filename}", type="positive") ui.notify(f"文件已上传: {filename}", type="positive")
if refs["current_file_label"]: if refs["current_file_label"]:
refs["current_file_label"].text = f"当前文件: {filename}" refs["current_file_label"].text = f"当前文件: {filename}"
@@ -130,20 +133,38 @@ def index():
refs["export_row"].clear() refs["export_row"].clear()
if not state["results"] or not refs["export_row"]: if not state["results"] or not refs["export_row"]:
return 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"]: with refs["export_row"]:
ui.button( ui.button(
"下载 Excel 对比表", "下载 Excel 对比表",
on_click=lambda: ui.download("wind_farm_design.xlsx"), on_click=lambda: ui.download("wind_farm_design.xlsx", download_name),
).props("icon=download") ).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(): def export_best_dxf():
dxf_name = "best_design.dxf"
if state["substation"] is not None: 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( export_to_dxf(
best_res["turbines"], best_res["turbines"],
state["substation"], state["substation"],
@@ -159,6 +180,89 @@ def index():
f'导出推荐方案 DXF ({best_res["name"]})', on_click=export_best_dxf f'导出推荐方案 DXF ({best_res["name"]})', on_click=export_best_dxf
).props("icon=architecture color=accent") ).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): def update_plot(result):
if refs["plot_container"]: if refs["plot_container"]:
refs["plot_container"].clear() refs["plot_container"].clear()
@@ -196,10 +300,15 @@ def index():
update_plot(selected_res) update_plot(selected_res)
ui.notify(f"已切换至方案: {selected_res['name']}") ui.notify(f"已切换至方案: {selected_res['name']}")
# 2. 通过设置 table 的 selected 属性来实现背景高亮 (监听点击事件驱动) # 2. 通过设置 table 的 selected 属性来实现背景高亮
if refs["results_table"]: if refs["results_table"]:
refs["results_table"].selected = [row] 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 from nicegui import run
import queue import queue
@@ -341,6 +450,20 @@ def index():
with ui.row().classes("w-full p-4 gap-4"): with ui.row().classes("w-full p-4 gap-4"):
with ui.card().classes("w-1/4 p-4 shadow-md"): with ui.card().classes("w-1/4 p-4 shadow-md"):
ui.label("配置面板").classes("text-xl font-semibold mb-4 border-b pb-2") 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") ui.label("1. 上传坐标文件 (.xlsx)").classes("font-medium")
refs["upload_widget"] = ui.upload( refs["upload_widget"] = ui.upload(
label="选择Excel文件", on_upload=handle_upload, auto_upload=True label="选择Excel文件", on_upload=handle_upload, auto_upload=True
@@ -399,4 +522,20 @@ def index():
refs["export_row"] = ui.row().classes("gap-4") 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)