refactor(packaging): PyInstaller资源路径统一适配get_resource_path

This commit is contained in:
DXC
2026-05-10 18:02:59 +08:00
parent 5a55be286f
commit 2a4a7ec7be
4 changed files with 74 additions and 47 deletions

View File

@ -5,9 +5,20 @@ Step5_5 面板 - 水质指数计算
""" """
import os import os
import sys
from pathlib import Path from pathlib import Path
from typing import Dict, List, Union from typing import Dict, List, Union
def get_resource_path(relative_path: str) -> str:
"""获取资源的绝对路径,适配 PyInstaller 打包环境。"""
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), relative_path)
)
import pandas as pd import pandas as pd
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout, QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout,
@ -153,10 +164,8 @@ class Step5_5Panel(QWidget):
self.setLayout(main_layout) self.setLayout(main_layout)
# 自动加载内置公式文件 # 自动加载内置公式文件
formula_csv_path = ( formula_csv_path = get_resource_path("data/sub/waterindex.csv")
Path(__file__).resolve().parent.parent / "model" / "waterindex.csv" if os.path.isfile(formula_csv_path):
)
if formula_csv_path.is_file():
self.formula_csv_widget.set_path(str(formula_csv_path)) self.formula_csv_widget.set_path(str(formula_csv_path))
self.refresh_formulas() self.refresh_formulas()

View File

@ -410,13 +410,27 @@ class Step9Panel(QWidget):
if not existing_out or not existing_out.strip(): if not existing_out or not existing_out.strip():
self.output_dir.set_path(output_dir) self.output_dir.set_path(output_dir)
# 5. 自动继承步骤1的水域掩膜作为边界文件 # 5. 自动探测原始矢量边界文件(.shp作为专题图底图
# 优先回溯 input-test/roi.shpgeopandas.read_file 仅支持矢量格式
if self.work_dir: if self.work_dir:
default_mask = Path(self.work_dir) / "1_water_mask" / "water_mask_from_shp.dat" possible_shp = None
if default_mask.exists(): candidates = [
existing_boundary = (self.boundary_file.get_path() or "").strip() Path(self.work_dir).parent / "input-test" / "roi.shp",
if not existing_boundary: Path(self.work_dir) / "roi.shp",
self.boundary_file.set_path(str(default_mask)) Path(self.work_dir).parent / "roi.shp",
]
for candidate in candidates:
if candidate.exists() and candidate.suffix.lower() == ".shp":
possible_shp = candidate
break
existing_boundary = (self.boundary_file.get_path() or "").strip()
if not existing_boundary and possible_shp:
self.boundary_file.set_path(str(possible_shp))
elif not existing_boundary:
# 未找到 .shp 时清空并提示用户手动选择矢量文件
self.boundary_file.set_path("")
print("⚠️ 提示:专题图生成模块需传入标准矢量边界文件 (.shp),请手动选择。")
except Exception as e: except Exception as e:
import traceback import traceback
print(f"{self.__class__.__name__}】自动填充失败,跳过: {e}") print(f"{self.__class__.__name__}】自动填充失败,跳过: {e}")

View File

@ -32,6 +32,18 @@ from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPalette, QColor, QPixmap
import sys import sys
import traceback import traceback
def get_resource_path(relative_path: str) -> str:
"""获取资源的绝对路径,适配 PyInstaller 打包环境。
打包后资源位于 sys._MEIPASS解压临时目录开发环境则基于 __file__ 向上三级。
"""
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), relative_path)
)
def global_exception_handler(exc_type, exc_value, exc_traceback): def global_exception_handler(exc_type, exc_value, exc_traceback):
print("\n" + "="*50) print("\n" + "="*50)
print("【严重错误拦截 - PyQt 崩溃死因】") print("【严重错误拦截 - PyQt 崩溃死因】")
@ -1398,19 +1410,8 @@ class WaterQualityGUI(QMainWindow):
} }
def get_icon_path(self, icon_filename): def get_icon_path(self, icon_filename):
""" """获取图标文件的完整路径(统一使用 get_resource_path"""
获取图标文件的完整路径 return get_resource_path(f"data/icons/{icon_filename}")
在开发环境中从../data/icons/获取在打包后从data/icons/获取
"""
if hasattr(sys, '_MEIPASS'):
# 打包后的环境
icon_dir = os.path.join(sys._MEIPASS, 'data', 'icons')
else:
# 开发环境
current_dir = os.path.dirname(os.path.abspath(__file__))
icon_dir = os.path.join(current_dir, '..', '..', 'data', 'icons')
return os.path.join(icon_dir, icon_filename)
def _disable_wheel_for_all_spinboxes(self): def _disable_wheel_for_all_spinboxes(self):
""" """
@ -1554,12 +1555,8 @@ class WaterQualityGUI(QMainWindow):
} }
""") """)
# 设置Logo图片路径 - 使用相对路径(打包兼容) # 设置Logo图片路径
from pathlib import Path logo_path = get_resource_path("data/icons/logo.png")
if hasattr(sys, '_MEIPASS'):
logo_path = os.path.join(sys._MEIPASS, 'data', 'icons', 'logo.png')
else:
logo_path = str(Path(__file__).parent.parent.parent / "data" / "icons" / "logo.png")
logo_pixmap = QPixmap(logo_path) logo_pixmap = QPixmap(logo_path)
if not logo_pixmap.isNull(): if not logo_pixmap.isNull():
@ -1692,10 +1689,7 @@ class WaterQualityGUI(QMainWindow):
banner_widget.setStyleSheet("margin: 0px; padding: 0px; border: none;") banner_widget.setStyleSheet("margin: 0px; padding: 0px; border: none;")
# 纯净底图路径(无水印文字) # 纯净底图路径(无水印文字)
if hasattr(sys, '_MEIPASS'): banner_path = get_resource_path("data/icons/Mega Water 1.0.jpg")
banner_path = os.path.join(sys._MEIPASS, 'data', 'icons', 'Mega Water 1.0.jpg')
else:
banner_path = str(Path(__file__).parent.parent.parent / "data" / "icons" / "Mega Water 1.0.jpg")
self.banner_pixmap = QPixmap(banner_path) self.banner_pixmap = QPixmap(banner_path)
if not self.banner_pixmap.isNull(): if not self.banner_pixmap.isNull():
@ -1822,14 +1816,14 @@ class WaterQualityGUI(QMainWindow):
self.step_list.addItem(stage_item) self.step_list.addItem(stage_item)
# 添加该阶段的所有步骤 # 添加该阶段的所有步骤
HIDDEN_STEP_IDS = {"step6_5", "step6_75", "step8_5", "step8_75"}
for step_id, step_display in steps: for step_id, step_display in steps:
if step_id in HIDDEN_STEP_IDS:
continue
item = QListWidgetItem(f" └─ {step_display}") item = QListWidgetItem(f" └─ {step_display}")
item.setData(Qt.UserRole, step_id) item.setData(Qt.UserRole, step_id)
# 隐藏4个冗余回归步骤树节点
if step_id in ("step6_5", "step6_75", "step8_5", "step8_75"):
item.setHidden(True)
self.step_name_map[step_display] = step_id self.step_name_map[step_display] = step_id
# 设置步骤项的样式 # 设置步骤项的样式

View File

@ -6,6 +6,7 @@
""" """
import os import os
import sys
import base64 import base64
import json import json
from dataclasses import dataclass from dataclasses import dataclass
@ -19,6 +20,15 @@ from docx.shared import Inches, Pt, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.section import WD_SECTION from docx.enum.section import WD_SECTION
from docx.oxml.ns import qn from docx.oxml.ns import qn
def get_resource_path(relative_path: str) -> str:
"""获取资源的绝对路径,适配 PyInstaller 打包环境。"""
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), relative_path)
)
from docx.oxml import OxmlElement from docx.oxml import OxmlElement
from docx.shared import RGBColor from docx.shared import RGBColor
import pandas as pd import pandas as pd
@ -848,8 +858,8 @@ class WaterQualityReportGenerator:
section.different_first_page_header_footer = True section.different_first_page_header_footer = True
# 1. 左上角图片(增大) - 使用相对路径 # 1. 左上角图片(增大) - 使用相对路径
cover_top_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "lica.png" cover_top_img_path = get_resource_path("data/icons/word/lica.png")
if cover_top_img_path.exists(): if os.path.isfile(cover_top_img_path):
try: try:
p = doc.add_paragraph() p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.LEFT p.alignment = WD_ALIGN_PARAGRAPH.LEFT
@ -897,8 +907,8 @@ class WaterQualityReportGenerator:
# 4. 底部图片(增大) - 使用相对路径 # 4. 底部图片(增大) - 使用相对路径
cover_bottom_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "fenmian.png" cover_bottom_img_path = get_resource_path("data/icons/word/fenmian.png")
if cover_bottom_img_path.exists(): if os.path.isfile(cover_bottom_img_path):
try: try:
p = doc.add_paragraph() p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
@ -960,8 +970,8 @@ class WaterQualityReportGenerator:
# 第一张图片 - 使用相对路径 # 第一张图片 - 使用相对路径
img1_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "屏幕截图 2026-03-31 144131.png" img1_path = get_resource_path("data/icons/word/屏幕截图 2026-03-31 144131.png")
if img1_path.exists(): if os.path.isfile(img1_path):
p = doc.add_paragraph() p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.add_run().add_picture(str(img1_path), width=Inches(6.0)) p.add_run().add_picture(str(img1_path), width=Inches(6.0))
@ -999,8 +1009,8 @@ class WaterQualityReportGenerator:
self._style_heading(h, level=1) self._style_heading(h, level=1)
# 插入图片 - 使用相对路径 # 插入图片 - 使用相对路径
processing_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "liucheng.png" processing_img_path = get_resource_path("data/icons/word/liucheng.png")
if processing_img_path.exists(): if os.path.isfile(processing_img_path):
p = doc.add_paragraph() p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.add_run().add_picture(str(processing_img_path), width=Inches(6.5)) p.add_run().add_picture(str(processing_img_path), width=Inches(6.5))
@ -1356,8 +1366,8 @@ class WaterQualityReportGenerator:
header_para = header.add_paragraph() header_para = header.add_paragraph()
# 1. 最左侧图片 - 使用相对路径 # 1. 最左侧图片 - 使用相对路径
header_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "lica.png" header_img_path = get_resource_path("data/icons/word/lica.png")
if header_img_path.exists(): if os.path.isfile(header_img_path):
try: try:
run_img = header_para.add_run() run_img = header_para.add_run()
run_img.add_picture(str(header_img_path), width=Inches(1.6)) run_img.add_picture(str(header_img_path), width=Inches(1.6))