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