diff --git a/src/gui/panels/step5_5_panel.py b/src/gui/panels/step5_5_panel.py index 0f24462..f2c2f7b 100644 --- a/src/gui/panels/step5_5_panel.py +++ b/src/gui/panels/step5_5_panel.py @@ -13,36 +13,55 @@ from src.gui.components.custom_widgets import FileSelectWidget from src.gui.styles import ModernStylesheet def get_resource_path(relative_path: str) -> str: + """适配开发与 PyInstaller 环境的路径获取逻辑""" if hasattr(sys, '_MEIPASS'): + # 打包后,文件会被平铺或按 tree 结构放入临时目录 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)) + + # 开发环境下:基于当前文件 (step5_5_panel.py) 的绝对路径进行回溯 + # 当前在 src/gui/panels/,目标在 src/gui/model/ + base_dir = Path(__file__).resolve().parent.parent / "model" + target_path = base_dir / os.path.basename(relative_path) + return str(target_path) class Step5_5Panel(QWidget): def __init__(self, parent=None): super().__init__(parent) self.index_checkboxes: Dict[str, QCheckBox] = {} - self.builtin_formula_path = get_resource_path("data/sub/waterindex.csv") + # 标识为 waterindex.csv,目录跳转逻辑在 get_resource_path 中 + self.builtin_formula_path = get_resource_path("waterindex.csv") + self.init_ui() + # 延迟一小会儿加载,确保UI框架已就绪 self._auto_load_formulas() def init_ui(self): main_layout = QVBoxLayout() main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setSpacing(10) - # 1. 数据文件(隐藏公式路径,自动填入训练数据) - data_group = QGroupBox("输入数据") - data_layout = QVBoxLayout() - self.training_data_widget = FileSelectWidget("训练数据CSV:", "CSV Files (*.csv)") - data_layout.addWidget(self.training_data_widget) + # 1. 路径展示区 (半透明只读) + path_group = QGroupBox("公式配置源 (内置)") + path_layout = QVBoxLayout() + self.formula_csv_widget = FileSelectWidget("内置CSV路径:", "CSV Files (*.csv)") + self.formula_csv_widget.set_path(self.builtin_formula_path) + self.formula_csv_widget.set_read_only(True) + # 视觉微调:提示用户这是内置的 + self.formula_csv_widget.line_edit.setStyleSheet("background-color: #f0f0f0; color: #666;") + path_layout.addWidget(self.formula_csv_widget) + path_group.setLayout(path_layout) + main_layout.addWidget(path_group) - self.formula_csv_widget = FileSelectWidget("公式配置:", "CSV Files (*.csv)") - self.formula_csv_widget.hide() # 界面隐身 - data_layout.addWidget(self.formula_csv_widget) - data_group.setLayout(data_layout) - main_layout.addWidget(data_group) + # 2. 训练数据输入 + input_group = QGroupBox("输入样本数据") + input_layout = QVBoxLayout() + self.training_data_widget = FileSelectWidget("特征提取CSV:", "CSV Files (*.csv)") + input_layout.addWidget(self.training_data_widget) + input_group.setLayout(input_layout) + main_layout.addWidget(input_group) - # 2. 公式选择区 - self.formula_group = QGroupBox("水质指数公式勾选") + # 3. 公式选择区 + self.formula_group = QGroupBox("待计算水质指数勾选") formula_outer_layout = QVBoxLayout() btn_layout = QHBoxLayout() @@ -53,63 +72,83 @@ class Step5_5Panel(QWidget): btn_layout.addWidget(self.select_all_btn) btn_layout.addWidget(self.deselect_all_btn) btn_layout.addStretch() + + self.refresh_button = QPushButton("手动重新加载公式") + self.refresh_button.clicked.connect(lambda: self.refresh_formulas(silent=False)) + btn_layout.addWidget(self.refresh_button) + formula_outer_layout.addLayout(btn_layout) - # 滚动显示区域 + # 核心滚动区 scroll = QScrollArea() scroll.setWidgetResizable(True) - scroll_content = QWidget() - self.formula_layout = QGridLayout(scroll_content) - scroll.setWidget(scroll_content) + scroll.setMinimumHeight(300) # 强制最小高度,防止塌陷 + self.scroll_content = QWidget() + self.formula_layout = QGridLayout(self.scroll_content) + self.formula_layout.setAlignment(Qt.AlignTop) # 靠顶对齐 + scroll.setWidget(self.scroll_content) formula_outer_layout.addWidget(scroll) self.formula_group.setLayout(formula_outer_layout) main_layout.addWidget(self.formula_group) - # 3. 输出设置 - output_group = QGroupBox("输出设置") + # 4. 输出与运行 + output_group = QGroupBox("结果输出") output_layout = QVBoxLayout() - self.output_file_widget = FileSelectWidget("输出CSV路径:", "CSV Files (*.csv)", mode="save") + self.output_file_widget = FileSelectWidget("保存路径:", "CSV Files (*.csv)", mode="save") output_layout.addWidget(self.output_file_widget) output_group.setLayout(output_layout) main_layout.addWidget(output_group) - # 4. 操作区 - self.enable_checkbox = QCheckBox("启用此步骤") + self.enable_checkbox = QCheckBox("启用计算流程") self.enable_checkbox.setChecked(True) main_layout.addWidget(self.enable_checkbox) - self.run_button = QPushButton("独立运行此步骤") + self.run_button = QPushButton("立即执行计算") self.run_button.setStyleSheet(ModernStylesheet.get_button_stylesheet('success')) + self.run_button.setMinimumHeight(40) self.run_button.clicked.connect(self.run_step) main_layout.addWidget(self.run_button) - main_layout.addStretch() self.setLayout(main_layout) def _auto_load_formulas(self): - if os.path.isfile(self.builtin_formula_path): - self.formula_csv_widget.set_path(self.builtin_formula_path) + """启动时自动加载逻辑""" + if os.path.exists(self.builtin_formula_path): self.refresh_formulas(silent=True) + else: + print(f"DEBUG: 自动加载失败,路径不存在: {self.builtin_formula_path}") def refresh_formulas(self, silent=False): - path = self.formula_csv_widget.get_path() - if not path or not os.path.exists(path): - if not silent: QMessageBox.warning(self, "警告", "内置公式文件丢失") + path = self.builtin_formula_path + if not os.path.exists(path): + if not silent: QMessageBox.warning(self, "错误", f"找不到内置公式文件:\n{path}") return + try: - # 清理旧项 + # 清理旧列表 for i in reversed(range(self.formula_layout.count())): - self.formula_layout.itemAt(i).widget().setParent(None) + widget = self.formula_layout.itemAt(i).widget() + if widget: widget.deleteLater() self.index_checkboxes.clear() - df = pd.read_csv(path) - # 修正:不使用 [1:] 切片,直接读取所有有效行 - formula_names = df['Formula_Name'].dropna().unique().tolist() + # 鲁棒性读取:尝试不同编码 + for encoding in ['utf-8', 'gbk', 'utf-8-sig']: + try: + df = pd.read_csv(path, encoding=encoding) + if 'Formula_Name' in df.columns: break + except: continue + + if 'Formula_Name' not in df.columns: + if not silent: QMessageBox.critical(self, "错误", "CSV文件缺少 'Formula_Name' 列") + return + + names = df['Formula_Name'].dropna().unique().tolist() row, col = 0, 0 - for name in formula_names: - name = name.strip() + for name in names: + name = str(name).strip() + if not name: continue cb = QCheckBox(name) cb.setChecked(True) self.index_checkboxes[name] = cb @@ -118,8 +157,13 @@ class Step5_5Panel(QWidget): if col >= 3: col = 0 row += 1 + + # 强制UI更新 + self.scroll_content.adjustSize() + print(f"✅ 成功加载 {len(self.index_checkboxes)} 个公式") + except Exception as e: - if not silent: QMessageBox.critical(self, "错误", f"解析公式失败: {e}") + if not silent: QMessageBox.critical(self, "加载失败", f"原因: {str(e)}") def select_all_formulas(self): for cb in self.index_checkboxes.values(): cb.setChecked(True) @@ -131,7 +175,7 @@ class Step5_5Panel(QWidget): selected = [n for n, cb in self.index_checkboxes.items() if cb.isChecked()] return { 'training_spectra_path': self.training_data_widget.get_path(), - 'formula_csv_file': self.formula_csv_widget.get_path(), + 'formula_csv_file': self.builtin_formula_path, 'formula_names': selected, 'output_file': self.output_file_widget.get_path(), 'enabled': self.enable_checkbox.isChecked() @@ -139,20 +183,17 @@ class Step5_5Panel(QWidget): def set_config(self, config): if 'training_spectra_path' in config: self.training_data_widget.set_path(config['training_spectra_path']) - if 'formula_csv_file' in config: - self.formula_csv_widget.set_path(config['formula_csv_file']) - self.refresh_formulas(silent=True) if 'formula_names' in config: sel = set(config['formula_names']) for n, cb in self.index_checkboxes.items(): cb.setChecked(n in sel) if 'output_file' in config: self.output_file_widget.set_path(config['output_file']) - if 'enabled' in config: self.enable_checkbox.setChecked(config['enabled']) + self.enable_checkbox.setChecked(config.get('enabled', True)) def update_from_config(self, work_dir=None, pipeline=None): if work_dir: self.work_dir = work_dir main = self.window() if hasattr(main, 'step5_panel'): - p5 = main.step5_panel.output_file.get_path() + p5 = main.step5_panel.output_file.get_path() # 修正:变量名对齐 if p5: if not os.path.isabs(p5): p5 = os.path.join(self.work_dir or '', p5).replace('\\', '/') self.training_data_widget.set_path(p5) @@ -164,7 +205,7 @@ class Step5_5Panel(QWidget): def run_step(self): config = self.get_config() if not config['training_spectra_path']: - QMessageBox.warning(self, "提示", "请选择输入数据") + QMessageBox.warning(self, "提示", "请先选择输入数据") return parent = self.parent() while parent and not hasattr(parent, 'run_single_step'): parent = parent.parent()