"""统一 action 注册表(Phase 3,蓝图 §1.6)。
将分散在 ``query_view`` / ``correction_queue_view`` / ``main_window`` 的
action 派发收敛为单一信源:
- ``action 名`` → ``视图方法名``(``ActionSpec.method``);
- 派发统一走 ``dispatch(view, action_name, item=None)``,经 ``getattr(view, method)``
解析并调用,替换 query_view 的三套内联字典、correction_queue 的 ``getattr`` 反射、
main_window 的硬编码导航 lambda。
设计要点:
- 注册表为**纯 Python、无 Qt 依赖**,便于单元测试;
- ``query_view`` 的 lambda action(``select_all`` / ``hide_selected`` / ``mark_inspected`` 等)
已提升为 ``_action_*`` 命名方法并注册(任务 1.2);工具栏与右键菜单现已 100% 经
``dispatch`` 派发,无内联 if/分支回退;``set_status_*`` / ``set_supply_type_*`` 等配置驱动
动态 action 经前缀通用 action(``set_status`` / ``set_supply_type``)承接。
- ``main_window`` 导航按钮(视图切换)已在 Phase 2.1 接入(``nav.*`` action → ``_nav_*`` 方法)。
"""
from __future__ import annotations
import inspect
from dataclasses import dataclass
from typing import Any
[文档]
@dataclass
class ActionSpec:
"""单个 UI action 的规范描述。
Attributes:
name: action 唯一名(与 ``ui.yaml`` / ``config.yaml`` 中 ``action`` 字段一致)。
method: 承载该 action 的视图方法名(经 ``getattr(view, method)`` 解析)。
label: 显示文本(工具栏/菜单用),可空(运行时取 yaml)。
icon: 图标资源标识,可空。
tooltip: 悬停提示,可空。
danger: 是否为危险操作(红色按钮),默认 False。
category: 分类(general / query / correction / nav),用于分组。
"""
name: str
method: str
label: str = ""
icon: str = ""
tooltip: str = ""
danger: bool = False
category: str = "general"
REGISTRY: dict[str, ActionSpec] = {}
[文档]
def register(spec: ActionSpec) -> None:
"""注册单个 action。重复注册后者覆盖前者。"""
REGISTRY[spec.name] = spec
[文档]
def register_many(specs: list[ActionSpec]) -> None:
"""批量注册。"""
for s in specs:
register(s)
[文档]
def get_spec(action_name: str) -> ActionSpec | None:
"""按名取 action 规范;不存在返回 None。"""
return REGISTRY.get(action_name)
# 通用前缀处理:set_status_* / set_supply_type_* 等配置驱动动作,由注册表通用 action 承接,
# 避免视图内联 if 分支(蓝图 §1.6 / §7.2 任务 1.2 全接入)。
_GENERIC_PREFIXES: dict[str, str] = {
"set_status_": "set_status",
"set_supply_type_": "set_supply_type",
}
[文档]
def dispatch(view: Any, action_name: str, item: dict | None = None) -> bool:
"""在 ``view`` 上派发 ``action_name``。
解析流程:``REGISTRY[action_name].method`` → ``getattr(view, method)`` → 调用。
命中且方法存在返回 True;action 未注册或视图无该方法返回 False(调用方据此告警)。
前缀通用处理:``set_status_待生产`` 等动态 action 名未精确注册时,回落到通用
``set_status`` / ``set_supply_type`` action(仍走注册表,无视图内联分支)。
方法签名适配:声明 ``item`` 形参的方法在 ``item`` 非空时接收上下文项;声明
``action_name`` 形参的方法始终接收 action 名(供 ``set_status`` 等从名解析取值)。
Args:
view: 承载 action 方法的视图实例。
action_name: action 名。
item: 可选上下文项(右键菜单项 dict),非空时传给方法。
Returns:
bool: 是否成功派发。
"""
spec = REGISTRY.get(action_name)
if spec is None:
for prefix, generic in _GENERIC_PREFIXES.items():
if action_name.startswith(prefix):
spec = REGISTRY.get(generic)
break
if spec is None:
return False
method = getattr(view, spec.method, None)
if method is None:
return False
# 按方法签名注入 item / action_name(向后兼容:方法无对应形参则不传)。
kwargs: dict[str, Any] = {}
try:
sig = inspect.signature(method)
params = [p for p in sig.parameters.values() if p.name != "self"]
except (TypeError, ValueError):
params = []
if item is not None and any(p.name == "item" for p in params):
kwargs["item"] = item
if any(p.name == "action_name" for p in params):
kwargs["action_name"] = action_name
if kwargs:
method(**kwargs)
else:
method()
return True
# ---------------------------------------------------------------------------
# action 目录(单一信源)
# 仅收录「方法名已存在」的 action;lambda 类(select_all 等)留待 Phase 3.2 提升为
# _action_* 命名方法后补充;main_window 导航留待 Phase 2.1。
# ---------------------------------------------------------------------------
register_many(
[
# --- query_view:工具栏 / 右键(命名方法部分)---
ActionSpec("search", "search", category="query"),
ActionSpec("invert_selection", "_invert_selection", category="query"),
ActionSpec("back_home", "_go_back_home", category="query"),
ActionSpec("fill_plan_no", "_batch_fill_plan_no", category="query"),
ActionSpec("set_year_month", "_set_year_month", category="query"),
ActionSpec("copy_cert_info", "_copy_cert_info", category="query"),
ActionSpec("edit_cert_fields", "_edit_cert_fields", category="query"),
ActionSpec("edit_certificate", "_edit_certificate", category="query"),
ActionSpec("goto_print", "_goto_print", category="query"),
ActionSpec("print_certificates", "_print_certificates", category="query"),
ActionSpec("preview_certificate", "_preview_certificate", category="query"),
ActionSpec("export_selected", "_export_selected", category="query"),
ActionSpec("batch_update_status", "_batch_update_production_status", category="query"),
ActionSpec("manual_number", "_manual_number", category="query"),
ActionSpec("auto_number", "_auto_number", category="query"),
ActionSpec("copy_number", "_copy_number", category="query"),
ActionSpec("delete_rows", "_delete_selected_records", category="query"),
# --- correction_queue_view(ui.yaml correction_queue.* 驱动,Phase 2.2)---
ActionSpec("release", "on_release_quarantine", category="correction"),
ActionSpec("delete", "on_delete_quarantine", danger=True, category="correction"),
ActionSpec("edit_and_release", "on_edit_and_release_quarantine", category="correction"),
ActionSpec("refresh", "refresh", category="correction"),
# --- query_view:原 lambda action(Phase 3.2 提升为命名方法)---
ActionSpec("select_all", "_action_select_all", category="query"),
ActionSpec("hide_selected", "_action_hide_selected", category="query"),
ActionSpec("hide_rows", "_action_hide_rows", category="query"),
ActionSpec("mark_inspected", "_action_mark_inspected", category="query"),
ActionSpec("mark_rejected", "_action_mark_rejected", category="query"),
ActionSpec("set_make", "_action_set_make", category="query"),
ActionSpec("set_buy", "_action_set_buy", category="query"),
ActionSpec("set_repair", "_action_set_repair", category="query"),
ActionSpec("toggle_needs_numbering", "_action_toggle_needs_numbering", category="query"),
# --- main_window 顶部导航(Phase 2.1,经 dispatch 派发到 MainWindow 的 _nav_* 方法)---
# action 名与 ui.yaml nav_buttons[].action 一一对应;方法名为 MainWindow 上的承载方法。
ActionSpec("nav.settings", "_nav_to_settings", label="系统设置", category="nav"),
ActionSpec("nav.import", "_nav_show_import", label="计划导入", category="nav"),
ActionSpec("nav.query", "_nav_to_query", label="查询与补充", category="nav"),
ActionSpec("nav.output", "_nav_to_output", label="输出", category="nav"),
ActionSpec("nav.history", "_nav_to_history", label="打印历史", category="nav"),
ActionSpec("nav.report", "_nav_to_report", label="报告生成", category="nav"),
ActionSpec(
"nav.correction_queue", "_nav_show_correction_queue", label="校正队列", category="nav"
),
ActionSpec(
"nav.lookup_tables", "_nav_show_lookup_tables", label="基础数据维护", category="nav"
),
# --- query_view:配置驱动通用动作(任务 1.2 全接入,消除视图内联 if/分支)---
# set_status_* / set_supply_type_* 经前缀通用 action(set_status / set_supply_type)承接,
# 无需为每个状态值单独注册。
ActionSpec("set_status", "_action_set_status", category="query"),
ActionSpec("set_supply_type", "_action_set_supply_type", category="query"),
ActionSpec("set_supply_type_make", "_action_set_make", category="query"),
ActionSpec("set_supply_type_buy", "_action_set_buy", category="query"),
ActionSpec("set_supply_type_repair", "_action_set_repair", category="query"),
ActionSpec("set_clear", "_action_set_clear", category="query"),
ActionSpec("shipped_archive", "_shipped_archive", category="query"),
ActionSpec("shipped_export_delete", "_shipped_export_delete", category="query"),
ActionSpec("open_lookup_table", "_action_open_lookup_table", category="query"),
ActionSpec("view_raw_execution", "_show_raw_execution_status", category="query"),
ActionSpec("mark_certificate_done", "_action_mark_document_done", category="query"),
ActionSpec("mark_nameplate_done", "_action_mark_document_done", category="query"),
ActionSpec("mark_test_report_done", "_action_mark_document_done", category="query"),
ActionSpec("mark_warranty_done", "_action_mark_document_done", category="query"),
]
)