fix: Step 7 路由 + 发送端字符串对齐(消除 KeyError)+ 配套防御性补强

核心修复

- panel_registry: step7_index class_ref 换绑 Step7View(最小侵入式对位,保持 step_id 保护全项目 18 处下游引用)

- step7_view._on_run_single_clicked: 'step7' → 'step7_index'(wrapped_config key + step_name 同步对齐,消除 PipelineScheduler 抛 KeyError "未注册的步骤: 'step7'")

配套防御性补强

- pipeline_executor.run_single_step_handler: 后台 is_running 时改 QMessageBox 警告 + LogMessage,防多次点击死锁

- worker_thread.run_single_step: 兼容嵌套/扁平 config 格式,嵌套子 dict 为空时回退扁平读取

- 公式 ListWidget layout 修复:setUniformItemSizes + stretch=1 + update(),消除 step7_view 加载后坍塌/不刷新
This commit is contained in:
DXC
2026-06-18 13:59:20 +08:00
parent f61a3dfb1d
commit f93dbeb848
5 changed files with 44 additions and 8 deletions

View File

@ -18,7 +18,7 @@ from src.gui.panels.step3_panel import Step3Panel
from src.gui.panels.step4_sampling_panel import Step4SamplingPanel from src.gui.panels.step4_sampling_panel import Step4SamplingPanel
from src.gui.panels.step5_clean_panel import Step5CleanPanel from src.gui.panels.step5_clean_panel import Step5CleanPanel
from src.gui.panels.step6_feature_panel import Step6FeaturePanel from src.gui.panels.step6_feature_panel import Step6FeaturePanel
from src.gui.panels.step7_index_panel import Step7IndexPanel from src.new.views.step7_view import Step7View
from src.gui.panels.step8_ml_train_panel import Step8MlTrainPanel from src.gui.panels.step8_ml_train_panel import Step8MlTrainPanel
from src.gui.panels.step9_ml_predict_panel import Step9MlPredictPanel from src.gui.panels.step9_ml_predict_panel import Step9MlPredictPanel
from src.gui.panels.step10_watercolor_panel import Step10WatercolorPanel from src.gui.panels.step10_watercolor_panel import Step10WatercolorPanel
@ -112,7 +112,7 @@ PANEL_REGISTRY = [
}, },
{ {
'step_id': 'step7_index', 'step_id': 'step7_index',
'class_ref': Step7IndexPanel, 'class_ref': Step7View,
'title': '水质光谱指数计算', 'title': '水质光谱指数计算',
'icon': '7.png', 'icon': '7.png',
'stage': '阶段二:样本数据准备', 'stage': '阶段二:样本数据准备',

View File

@ -409,6 +409,8 @@ class PipelineExecutor(QObject):
step_name = data.get('step_name') step_name = data.get('step_name')
config = data.get('config') config = data.get('config')
print(f"==== Executor 收到单步请求: {step_name} ====", flush=True)
if not step_name: if not step_name:
global_event_bus.publish('LogMessage', { global_event_bus.publish('LogMessage', {
'message': '[单步执行] 请求缺少 step_name忽略', 'message': '[单步执行] 请求缺少 step_name忽略',
@ -416,6 +418,18 @@ class PipelineExecutor(QObject):
}) })
return return
# ★ 防死锁:若已有 Worker 在运行,不静默吞掉,而是通知用户
if self.is_running:
global_event_bus.publish('LogMessage', {
'message': f'[单步执行] 后台正在运行中,无法启动 {step_name}。请等待当前任务完成或手动停止后再试。',
'level': 'warning',
})
QMessageBox.warning(
self.parent(), "后台忙碌",
f"后台正在运行中,无法启动 {step_name}\n\n请等待当前任务完成,或点击「停止」按钮后再试。"
)
return
global_event_bus.publish('LogMessage', { global_event_bus.publish('LogMessage', {
'message': f'[单步执行] 收到 {step_name} 的执行请求', 'message': f'[单步执行] 收到 {step_name} 的执行请求',
'level': 'info', 'level': 'info',

View File

@ -338,8 +338,18 @@ class WorkerThread(QThread):
pass pass
def run_single_step(self, scheduler, step_name, config): def run_single_step(self, scheduler, step_name, config):
"""使用新调度器运行单个步骤。""" """使用新调度器运行单个步骤。
step_config = dict(config.get(step_name, {}))
config 兼容两种格式:
嵌套格式:{'step7': {'training_csv_path': ..., ...}}
扁平格式:{'training_csv_path': ..., 'formula_names': [...], ...}
优先检测嵌套格式;若 step_name 对应的子 dict 为空,则回退到扁平格式。
"""
nested = config.get(step_name)
if nested and isinstance(nested, dict) and len(nested) > 0:
step_config = dict(nested)
else:
step_config = dict(config)
# 透传外部预训练模型(非空才覆盖) # 透传外部预训练模型(非空才覆盖)
for key in ('_external_model', '_external_model_path', for key in ('_external_model', '_external_model_path',

View File

@ -123,6 +123,7 @@ class Step7IndexPanel(QWidget):
self.formula_list = QListWidget() self.formula_list = QListWidget()
self.formula_list.setSelectionMode(QAbstractItemView.MultiSelection) self.formula_list.setSelectionMode(QAbstractItemView.MultiSelection)
self.formula_list.setMinimumHeight(300)
self.formula_list.itemChanged.connect(self._on_item_changed) self.formula_list.itemChanged.connect(self._on_item_changed)
self.formula_layout.addWidget(self.formula_list) self.formula_layout.addWidget(self.formula_list)

View File

@ -129,9 +129,10 @@ class Step7View(BaseView):
self.formula_list.setSelectionMode(QListWidget.MultiSelection) self.formula_list.setSelectionMode(QListWidget.MultiSelection)
self.formula_list.setMinimumHeight(300) self.formula_list.setMinimumHeight(300)
self.formula_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.formula_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.formula_list.setUniformItemSizes(True)
# view 层不需要 itemChanged 副作用service 接管时再启用 # view 层不需要 itemChanged 副作用service 接管时再启用
self.formula_list.blockSignals(True) self.formula_list.blockSignals(True)
formula_outer_layout.addWidget(self.formula_list) formula_outer_layout.addWidget(self.formula_list, stretch=1)
self.formula_group.setLayout(formula_outer_layout) self.formula_group.setLayout(formula_outer_layout)
layout.addWidget(self.formula_group) layout.addWidget(self.formula_group)
@ -233,6 +234,8 @@ class Step7View(BaseView):
self.index_checkboxes[name] = item self.index_checkboxes[name] = item
self.formula_list.blockSignals(False) self.formula_list.blockSignals(False)
self.formula_list.adjustSize() self.formula_list.adjustSize()
self.formula_list.updateGeometry()
self.update()
for btn in (self.select_all_btn, self.deselect_all_btn, for btn in (self.select_all_btn, self.deselect_all_btn,
self.select_ratio_btn, self.select_conc_btn, self.refresh_button): self.select_ratio_btn, self.select_conc_btn, self.refresh_button):
@ -319,6 +322,14 @@ class Step7View(BaseView):
# 执行入口 # 执行入口
# ------------------------------------------------------------------ # ------------------------------------------------------------------
def _on_run_single_clicked(self): def _on_run_single_clicked(self):
from src.gui.core.event_bus import global_event_bus import traceback
config = self.get_config() print("==== Step 7 计算按钮物理按下 ====", flush=True)
global_event_bus.publish('RequestRunSingleStep', {'step_name': 'step7', 'config': config}) try:
from src.gui.core.event_bus import global_event_bus
config = self.get_config()
wrapped_config = {'step7_index': config}
global_event_bus.publish('RequestRunSingleStep', {'step_name': 'step7_index', 'config': wrapped_config})
except Exception as e:
traceback.print_exc()
from PyQt5.QtWidgets import QMessageBox
QMessageBox.critical(self, "Step7 发射失败", f"收集配置或发送请求时异常:\n\n{e}")