feat: 优化GUI用户体验和打包配置
主要改进: 1. GUI界面优化 - 自定义文件上传显示组件,替换默认列表为更美观的卡片式展示 - 支持环境变量 PROJECT_TEMP_DIR 配置临时目录路径 - 优化文件导出路径管理,统一使用临时目录 - 改进端口查找逻辑,从8082开始避免常用端口冲突 - 修复打包后无控制台模式的stdout/stderr处理 2. 打包配置改进 - 更新Makefile使用.spec文件进行打包 - 添加nicegui-pack打包选项 - 优化clean命令,使用Python跨平台清理 3. 代码优化 - 注释掉main.py中的详细统计信息打印 - 改进打包环境的日志配置方式
This commit is contained in:
39
Makefile
39
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
|
||||
99
gui.py
99
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():
|
||||
<style>
|
||||
.hide-selection-column .q-table__selection { display: none; }
|
||||
.hide-selection-column .q-table tr.selected { background-color: #e3f2fd !important; }
|
||||
.no-list .q-uploader__list { display: none !important; }
|
||||
</style>
|
||||
""")
|
||||
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:
|
||||
@@ -203,6 +214,10 @@ def index():
|
||||
|
||||
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)
|
||||
|
||||
8
main.py
8
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)
|
||||
|
||||
Reference in New Issue
Block a user