From 751bdef245c60a99cf429283e8d873f742b4d523 Mon Sep 17 00:00:00 2001 From: dmy Date: Mon, 5 Jan 2026 17:09:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96GUI=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E5=92=8C=E6=89=93=E5=8C=85=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要改进: 1. GUI界面优化 - 自定义文件上传显示组件,替换默认列表为更美观的卡片式展示 - 支持环境变量 PROJECT_TEMP_DIR 配置临时目录路径 - 优化文件导出路径管理,统一使用临时目录 - 改进端口查找逻辑,从8082开始避免常用端口冲突 - 修复打包后无控制台模式的stdout/stderr处理 2. 打包配置改进 - 更新Makefile使用.spec文件进行打包 - 添加nicegui-pack打包选项 - 优化clean命令,使用Python跨平台清理 3. 代码优化 - 注释掉main.py中的详细统计信息打印 - 改进打包环境的日志配置方式 --- Makefile | 39 +++++++++++----------- gui.py | 99 +++++++++++++++++++++++++++++++++++++++----------------- main.py | 8 ++--- 3 files changed, 95 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index e5f37d1..819226f 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,31 @@ .PHONY: help clean build rebuild -# 默认目标 +# ĬĿ help: - @echo "海上风电场集电线路设计优化系统 - 打包脚本" + @echo "Ϸ糡·Żϵͳ - ű" @echo "" - @echo "可用命令:" - @echo " make build - 打包成独立的exe程序" - @echo " make rebuild - 清理后重新打包" - @echo " make clean - 清理打包生成的临时文件" - @echo " make help - 显示此帮助信息" + @echo ":" + @echo " make build - ʹ .spec ļɵļ exe " + @echo " make rebuild - ¹" + @echo " make clean - ɵʱļͻ" + @echo " make help - ʾ˰Ϣ" -# 打包成独立的exe程序 +# ɵļexe +# ʹ --clean PyInstaller 棬-y Զ build: - @echo "开始打包程序..." - uv run pyinstaller build.spec - @echo "打包完成!" - @echo "可执行文件位于: dist/海上风电场集电线路设计优化系统.exe" + @echo "ʼ..." + uv run pyinstaller --clean -y "Ϸ糡·Żϵͳ.spec" + @echo "ɣ" + @echo "ִļλ: dist/Ϸ糡·Żϵͳ.exe" -# 清理打包生成的临时文件 +# ɵʱļ clean: - @echo "清理打包临时文件..." - @if exist build rmdir /s /q build - @if exist dist rmdir /s /q dist - @echo "清理完成!" + @echo "ʱļ..." + @uv run python -c "import shutil, pathlib; [shutil.rmtree(p) for p in pathlib.Path('.').rglob('__pycache__')]; shutil.rmtree('build', ignore_errors=True); shutil.rmtree('dist', ignore_errors=True)" + @echo "ɣ" +nice: + uv run nicegui-pack --onefile --name "Ϸ糡·Żϵͳ" gui.py --onefile --windowed -# 清理后重新打包 + +# ¹ rebuild: clean build \ No newline at end of file diff --git a/gui.py b/gui.py index c157192..28b2d96 100644 --- a/gui.py +++ b/gui.py @@ -4,6 +4,7 @@ import io import contextlib import tempfile import matplotlib.pyplot as plt +import matplotlib.backends.backend_svg from nicegui import ui, events from main import ( compare_design_methods, @@ -32,6 +33,8 @@ class Logger(io.StringIO): # 状态变量 +# 优先从环境变量 PROJECT_TEMP_DIR 读取,否则使用系统默认临时目录 +_base_temp = os.environ.get("PROJECT_TEMP_DIR", tempfile.gettempdir()) state = { "excel_path": None, "original_filename": None, @@ -40,7 +43,7 @@ state = { "turbines": None, "cable_specs": None, "system_params": None, - "temp_dir": os.path.join(tempfile.gettempdir(), "windfarm_gui_uploads"), + "temp_dir": os.path.join(_base_temp, "windfarm_gui_uploads"), } # 确保临时目录存在 @@ -55,6 +58,7 @@ def index(): """) ui.query("body").style( @@ -71,7 +75,7 @@ def index(): "status_label": None, "upload_widget": None, "run_btn": None, - "current_file_label": None, + "current_file_container": None, # 替换 label 为 container "info_container": None, # 新增信息展示容器 } @@ -189,8 +193,15 @@ def index(): 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}" + + # 更新文件显示区域 + if refs["current_file_container"]: + refs["current_file_container"].clear() + with refs["current_file_container"]: + with ui.row().classes('items-center w-full bg-blue-50 p-2 rounded border border-blue-200'): + ui.icon('description', color='primary').classes('text-xl mr-2') + ui.label(filename).classes('font-medium text-gray-700 flex-grow') + ui.icon('check_circle', color='positive') # 加载数据 try: @@ -202,6 +213,10 @@ def index(): state["system_params"] = {} update_info_panel() + + # 清空上传组件列表,以便下次选择(配合 .no-list CSS 使用) + if refs["upload_widget"]: + refs["upload_widget"].reset() except Exception as ex: ui.notify(f"上传处理失败: {ex}", type="negative") @@ -215,9 +230,14 @@ def index(): # 获取文件名基础前缀 file_prefix = "wind_farm" download_name = "wind_farm_design_result.xlsx" - if state.get("original_filename"): + + # 确定主对比 Excel 的生成路径 (对应 main.py 中的生成逻辑) + if state.get("excel_path"): file_prefix = os.path.splitext(state["original_filename"])[0] download_name = f"{file_prefix}_result.xlsx" + main_excel_path = os.path.join(state["temp_dir"], f"{file_prefix}_design.xlsx") + else: + main_excel_path = "wind_farm_design.xlsx" # 寻找推荐方案:优先 Scenario 1 的最低成本,否则取全局最低成本 scenario1_results = [r for r in state["results"] if "Scenario 1" in r["name"]] @@ -229,7 +249,7 @@ def index(): with refs["export_row"]: ui.button( "下载 Excel 对比表", - on_click=lambda: ui.download("wind_farm_design.xlsx", download_name), + on_click=lambda: ui.download(main_excel_path, download_name), ).props("icon=download") def export_best_dxf(): @@ -242,7 +262,7 @@ def index(): if c.isalnum() or c in (" ", "-", "_") ] ).strip() - dxf_name = f"{file_prefix}_best_{safe_name}.dxf" + dxf_name = os.path.join(state["temp_dir"], f"{file_prefix}_best_{safe_name}.dxf") export_to_dxf( best_res["turbines"], @@ -279,7 +299,7 @@ def index(): if c.isalnum() or c in (" ", "-", "_") ] ).strip() - dxf_name = f"{file_prefix}_{safe_name}.dxf" + dxf_name = os.path.join(state["temp_dir"], f"{file_prefix}_{safe_name}.dxf") export_to_dxf( selected_res["turbines"], @@ -309,18 +329,11 @@ def index(): import zipfile # 1. 确定文件名 - zip_filename = f"{file_prefix}_all_results.zip" + zip_filename = os.path.join(state["temp_dir"], f"{file_prefix}_all_results.zip") excel_result_name = f"{file_prefix}_summary.xlsx" # 推断 main.py 生成的原始 Excel 路径 - generated_excel_path = "wind_farm_design.xlsx" - - if state.get("original_filename"): - if state.get("excel_path"): - dir_name = os.path.dirname(state["excel_path"]) - generated_excel_path = os.path.join( - dir_name, f"{file_prefix}_design.xlsx" - ) + generated_excel_path = main_excel_path try: with zipfile.ZipFile( @@ -332,7 +345,7 @@ def index(): else: # 尝试重新生成 try: - temp_excel = "temp_export.xlsx" + temp_excel = os.path.join(state["temp_dir"], "temp_export.xlsx") export_all_scenarios_to_excel( state["results"], temp_excel ) @@ -350,7 +363,7 @@ def index(): if c.isalnum() or c in (" ", "-", "_") ] ).strip() - dxf_name = f"{file_prefix}_{safe_name}.dxf" + dxf_name = os.path.join(state["temp_dir"], f"{file_prefix}_{safe_name}.dxf") export_to_dxf( res["turbines"], state["substation"], @@ -367,6 +380,7 @@ def index(): 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" ) @@ -611,10 +625,18 @@ def index(): ).props("icon=file_download outline color=primary") ui.label("1. 上传坐标文件 (.xlsx)").classes("font-medium") + + # 使用 .no-list CSS 隐藏 Quasar 默认列表,完全自定义文件显示 refs["upload_widget"] = ui.upload( - label="选择Excel文件", on_upload=handle_upload, auto_upload=True - ).classes("w-full mb-2") - # refs['current_file_label'] = ui.label('未选择文件').classes('text-xs text-gray-500 mb-4 italic') + label="选择Excel文件", + on_upload=handle_upload, + auto_upload=True + ).classes("w-full mb-2 no-list") + + # 自定义文件显示容器 + refs['current_file_container'] = ui.column().classes('w-full mb-4') + with refs['current_file_container']: + ui.label('未选择文件').classes('text-xs text-gray-500 italic ml-1') refs["run_btn"] = ( ui.button( @@ -679,14 +701,15 @@ def index(): refs["export_row"] = ui.row().classes("gap-4") -def find_available_port(start_port=8080, max_attempts=10): +def find_available_port(start_port=8080, max_attempts=100): """尝试寻找可用的端口""" 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)) + # 尝试绑定到 127.0.0.1,这是最常用的本地开发地址 + s.bind(("127.0.0.1", port)) return port except OSError: continue @@ -694,15 +717,33 @@ def find_available_port(start_port=8080, max_attempts=10): # 自动寻找可用端口,避免端口冲突 -target_port = find_available_port(8080) +target_port = find_available_port(8082) # 从 8082 开始,避开常用的 8080 # 检测是否为打包后的exe程序 import sys if getattr(sys, 'frozen', False): + # 修复无控制台模式下 stdout/stderr 为 None 导致的 logging 错误 + class NullWriter: + def write(self, text): + pass + def flush(self): + pass + def isatty(self): + return False + + if sys.stdout is None: + sys.stdout = NullWriter() + if sys.stderr is None: + sys.stderr = NullWriter() + # 打包环境下禁用日志配置,避免在无控制台模式下出现错误 - import logging - logging.disable(logging.CRITICAL) - ui.run(title="海上风电场集电线路优化", port=target_port, log_level=None) + import logging.config + logging.config.dictConfig({ + 'version': 1, + 'disable_existing_loggers': True, + }) + ui.run(title="海上风电场集电线路优化", host='127.0.0.1', port=target_port, reload=False, window_size=(1280, 800),native=True) else: # 普通使用环境保留日志功能 - ui.run(title="海上风电场集电线路优化", port=target_port) + # ui.run(title="海上风电场集电线路优化", host='127.0.0.1', reload=True, port=target_port, native=False) + ui.run(title="海上风电场集电线路优化", host='127.0.0.1', port=target_port, reload=True, window_size=(1280, 800),native=True) diff --git a/main.py b/main.py index 9cbd15c..c12a736 100644 --- a/main.py +++ b/main.py @@ -1051,10 +1051,10 @@ def visualize_design(turbines, substation, connections, title, ax=None, show_cos legend_handles[section] = line # 打印统计信息 - if is_detailed: - print(f"[{title.splitlines()[0]}] 电缆统计:") - for section in sorted(cable_counts.keys()): - print(f" {section}mm²: {cable_counts[section]} 条") + # if is_detailed: + # print(f"[{title.splitlines()[0]}] 电缆统计:") + # for section in sorted(cable_counts.keys()): + # print(f" {section}mm²: {cable_counts[section]} 条") # 设置图形属性 ax.set_title(title, fontsize=10)