feat: 增强导出功能和端口自动分配
This commit is contained in:
157
gui.py
157
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)
|
||||
|
||||
Reference in New Issue
Block a user