diff --git a/src/gui/panels/step5_5_panel.py b/src/gui/panels/step5_5_panel.py index 7742cb5..89427c0 100644 --- a/src/gui/panels/step5_5_panel.py +++ b/src/gui/panels/step5_5_panel.py @@ -5,9 +5,20 @@ Step5_5 面板 - 水质指数计算 """ import os +import sys from pathlib import Path 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 from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QGroupBox, QFormLayout, QGridLayout, @@ -153,10 +164,8 @@ class Step5_5Panel(QWidget): self.setLayout(main_layout) # 自动加载内置公式文件 - formula_csv_path = ( - Path(__file__).resolve().parent.parent / "model" / "waterindex.csv" - ) - if formula_csv_path.is_file(): + formula_csv_path = get_resource_path("data/sub/waterindex.csv") + if os.path.isfile(formula_csv_path): self.formula_csv_widget.set_path(str(formula_csv_path)) self.refresh_formulas() diff --git a/src/gui/panels/step9_panel.py b/src/gui/panels/step9_panel.py index 9798053..c35a9d1 100644 --- a/src/gui/panels/step9_panel.py +++ b/src/gui/panels/step9_panel.py @@ -410,13 +410,27 @@ class Step9Panel(QWidget): if not existing_out or not existing_out.strip(): self.output_dir.set_path(output_dir) - # 5. 自动继承步骤1的水域掩膜作为边界文件 + # 5. 自动探测原始矢量边界文件(.shp)作为专题图底图 + # 优先回溯 input-test/roi.shp,geopandas.read_file 仅支持矢量格式 if self.work_dir: - default_mask = Path(self.work_dir) / "1_water_mask" / "water_mask_from_shp.dat" - if default_mask.exists(): - existing_boundary = (self.boundary_file.get_path() or "").strip() - if not existing_boundary: - self.boundary_file.set_path(str(default_mask)) + possible_shp = None + candidates = [ + Path(self.work_dir).parent / "input-test" / "roi.shp", + Path(self.work_dir) / "roi.shp", + 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: import traceback print(f"【{self.__class__.__name__}】自动填充失败,跳过: {e}") diff --git a/src/gui/water_quality_gui.py b/src/gui/water_quality_gui.py index eb11796..54e3a96 100644 --- a/src/gui/water_quality_gui.py +++ b/src/gui/water_quality_gui.py @@ -32,6 +32,18 @@ from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPalette, QColor, QPixmap import sys 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): print("\n" + "="*50) print("【严重错误拦截 - PyQt 崩溃死因】") @@ -1398,19 +1410,8 @@ class WaterQualityGUI(QMainWindow): } def get_icon_path(self, 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) + """获取图标文件的完整路径(统一使用 get_resource_path)。""" + return get_resource_path(f"data/icons/{icon_filename}") def _disable_wheel_for_all_spinboxes(self): """ @@ -1554,12 +1555,8 @@ class WaterQualityGUI(QMainWindow): } """) - # 设置Logo图片路径 - 使用相对路径(打包兼容) - from pathlib import Path - 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图片路径 + logo_path = get_resource_path("data/icons/logo.png") logo_pixmap = QPixmap(logo_path) if not logo_pixmap.isNull(): @@ -1692,10 +1689,7 @@ class WaterQualityGUI(QMainWindow): banner_widget.setStyleSheet("margin: 0px; padding: 0px; border: none;") # 纯净底图路径(无水印文字) - if hasattr(sys, '_MEIPASS'): - 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") + banner_path = get_resource_path("data/icons/Mega Water 1.0.jpg") self.banner_pixmap = QPixmap(banner_path) if not self.banner_pixmap.isNull(): @@ -1822,14 +1816,14 @@ class WaterQualityGUI(QMainWindow): self.step_list.addItem(stage_item) # 添加该阶段的所有步骤 + HIDDEN_STEP_IDS = {"step6_5", "step6_75", "step8_5", "step8_75"} for step_id, step_display in steps: + if step_id in HIDDEN_STEP_IDS: + continue + item = QListWidgetItem(f" └─ {step_display}") 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 # 设置步骤项的样式 diff --git a/src/postprocessing/report_word.py b/src/postprocessing/report_word.py index 80e1e57..1cc7e00 100644 --- a/src/postprocessing/report_word.py +++ b/src/postprocessing/report_word.py @@ -6,6 +6,7 @@ """ import os +import sys import base64 import json 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.section import WD_SECTION 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.shared import RGBColor import pandas as pd @@ -848,8 +858,8 @@ class WaterQualityReportGenerator: section.different_first_page_header_footer = True # 1. 左上角图片(增大) - 使用相对路径 - cover_top_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "lica.png" - if cover_top_img_path.exists(): + cover_top_img_path = get_resource_path("data/icons/word/lica.png") + if os.path.isfile(cover_top_img_path): try: p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.LEFT @@ -897,8 +907,8 @@ class WaterQualityReportGenerator: # 4. 底部图片(增大) - 使用相对路径 - cover_bottom_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "fenmian.png" - if cover_bottom_img_path.exists(): + cover_bottom_img_path = get_resource_path("data/icons/word/fenmian.png") + if os.path.isfile(cover_bottom_img_path): try: p = doc.add_paragraph() 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" - if img1_path.exists(): + img1_path = get_resource_path("data/icons/word/屏幕截图 2026-03-31 144131.png") + if os.path.isfile(img1_path): p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.add_run().add_picture(str(img1_path), width=Inches(6.0)) @@ -999,8 +1009,8 @@ class WaterQualityReportGenerator: self._style_heading(h, level=1) # 插入图片 - 使用相对路径 - processing_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "liucheng.png" - if processing_img_path.exists(): + processing_img_path = get_resource_path("data/icons/word/liucheng.png") + if os.path.isfile(processing_img_path): p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.add_run().add_picture(str(processing_img_path), width=Inches(6.5)) @@ -1356,8 +1366,8 @@ class WaterQualityReportGenerator: header_para = header.add_paragraph() # 1. 最左侧图片 - 使用相对路径 - header_img_path = Path(__file__).parent.parent.parent / "data" / "icons" / "word" / "lica.png" - if header_img_path.exists(): + header_img_path = get_resource_path("data/icons/word/lica.png") + if os.path.isfile(header_img_path): try: run_img = header_para.add_run() run_img.add_picture(str(header_img_path), width=Inches(1.6))