certflow.widgets.filter_bar 源代码

"""筛选条容器(通用可复用 UI 构件,归 widgets 包)。

管理 N 个 :class:`FilterRowWidget` + 「添加条件」按钮 + 可选快查框,
对外发出 ``conditions_changed`` 信号,并汇总为统一条件契约
``{field: {value, operator}}``。

设计要点(对齐 docs/BLUEPRINT.md §五 wf5):

- **数据源无关**:行所需的下拉加载 / 日期选择 / 样式回调通过构造参数透传给
  每个 ``FilterRowWidget``;容器本身不触碰具体视图或 Controller。
- **统一条件契约**:``get_conditions()`` 与 ``query_view.get_filters`` 输出格式一致,
  便于等价替换。
- **快查框**:可选 ``QuickSearch``(复用导入对话框的模糊匹配交互),选中项通过
  ``quick_search_callback`` 回调上抛,由宿主视图决定如何定位(如级联下拉 / 内存过滤)。
"""

from __future__ import annotations

from PySide6.QtCore import Signal
from PySide6.QtWidgets import QComboBox, QGridLayout, QHBoxLayout, QPushButton, QWidget

from certflow.widgets.filter_row import FilterRowWidget
from certflow.widgets.quick_search import QuickSearch


[文档] class FilterBar(QWidget): """筛选条件容器。""" conditions_changed = Signal() def __init__( self, parent: QWidget | None = None, *, columns: list[dict] | None = None, field_op_map: dict | None = None, field_choices_map: dict | None = None, operators: dict | None = None, is_dark_theme_fn=None, load_combo_values_fn=None, open_date_picker_fn=None, date_button_style_fn=None, grid_cols: int = 2, show_quick_search: bool = False, quick_search_placeholder: str = "", quick_search_callback=None, quick_search_typing_callback=None, show_preset_selector: bool = False, presets: list | None = None, preset_changed_callback=None, ) -> None: super().__init__(parent) self._columns = list(columns or []) self._field_op_map = field_op_map or {} self._field_choices_map = field_choices_map or {} self._operators = operators or {} self._is_dark_theme_fn = is_dark_theme_fn self._load_combo_values_fn = load_combo_values_fn self._open_date_picker_fn = open_date_picker_fn self._date_button_style_fn = date_button_style_fn self._grid_cols = max(1, grid_cols) self._presets = list(presets or []) self._rows: list[FilterRowWidget] = [] root = QHBoxLayout(self) root.setContentsMargins(0, 0, 0, 0) self._rows_widget = QWidget() self._grid = QGridLayout(self._rows_widget) self._grid.setContentsMargins(0, 0, 0, 0) root.addWidget(self._rows_widget, 1) # 预设选择器(preset 下拉,方案 B):切换 preset 改变快查框字段集 if show_preset_selector and self._presets: self._preset_combo = QComboBox() self._preset_combo.addItems([p.get("name", "") for p in self._presets]) self._preset_combo.currentTextChanged.connect( lambda name: preset_changed_callback(name) if preset_changed_callback else None ) root.addWidget(self._preset_combo) else: self._preset_combo = None if show_quick_search: self._quick_search = QuickSearch( None, placeholder=quick_search_placeholder, min_width=260, ) self._quick_search.activated.connect( lambda text: quick_search_callback(text) if quick_search_callback else None ) # 模糊模式:用户键入时实时转发(驱动下拉模糊匹配 + 宿主防抖初步筛选) self._quick_search.typing.connect( lambda text: ( quick_search_typing_callback(text) if quick_search_typing_callback else None ) ) root.addWidget(self._quick_search) else: self._quick_search = None self._add_btn = QPushButton("+ 添加条件") self._add_btn.setFixedHeight(24) self._add_btn.clicked.connect(lambda: self.add_row()) root.addWidget(self._add_btn) # ============================================================ # 列定义(随视图切换更新) # ============================================================
[文档] def set_columns(self, columns: list[dict]) -> None: """更新可用字段列表(视图切换时调用),并刷新所有行的字段下拉。""" self._columns = list(columns) for row in self._rows: row.set_columns(self._columns)
# ============================================================ # 行管理 # ============================================================
[文档] def add_row(self, field_name=None, operator=None, value=None) -> FilterRowWidget: """添加一行筛选条件,返回新建的 ``FilterRowWidget``。""" row = FilterRowWidget( None, columns=self._columns, field_op_map=self._field_op_map, field_choices_map=self._field_choices_map, operators=self._operators, is_dark_theme_fn=self._is_dark_theme_fn, load_combo_values_fn=self._load_combo_values_fn, open_date_picker_fn=self._open_date_picker_fn, date_button_style_fn=self._date_button_style_fn, field_name=field_name, operator=operator, value=value, ) row.value_changed.connect(self._on_row_value_changed) row.structure_changed.connect(self._on_row_structure_changed) row.remove_requested.connect(lambda: self._remove_row_widget(row)) self._rows.append(row) self._reposition() self.conditions_changed.emit() return row
def _on_row_value_changed(self) -> None: self.conditions_changed.emit() def _on_row_structure_changed(self) -> None: self.conditions_changed.emit() def _remove_row_widget(self, row: FilterRowWidget) -> None: if row in self._rows: self._rows.remove(row) self._grid.removeWidget(row) row.deleteLater() self._reposition() self.ensure_at_least_one_row() self.conditions_changed.emit()
[文档] def remove_row(self, widget: QWidget) -> None: """按控件引用删除一行(供宿主视图快筛逻辑调用)。""" if isinstance(widget, FilterRowWidget): self._remove_row_widget(widget)
[文档] def clear_rows(self) -> None: """清空所有筛选行。""" for row in list(self._rows): self._grid.removeWidget(row) row.deleteLater() self._rows = []
[文档] def ensure_at_least_one_row(self) -> None: """保证至少有一个空筛选行。""" if not self._rows: self.add_row()
def _reposition(self) -> None: for i, row in enumerate(self._rows): r = i // self._grid_cols c = i % self._grid_cols self._grid.removeWidget(row) self._grid.addWidget(row, r, c) # ============================================================ # 条件汇总 # ============================================================
[文档] def rows_data(self) -> list[dict]: """返回所有行的 row_data 字典列表(迁移期兼容)。""" return [r.row_data() for r in self._rows]
[文档] def rows(self) -> list[FilterRowWidget]: return list(self._rows)
[文档] def get_conditions(self) -> dict: """汇总为统一条件契约 ``{field: {value, operator}}``。""" conditions: dict = {} for row in self._rows: cond = row.get_condition() if cond: conditions.update(cond) return conditions
[文档] def collect_conditions(self) -> list[dict]: """汇总为列表形式 ``[{field, operator, value}, ...]``(保存 / 加载用)。""" out: list[dict] = [] for row in self._rows: cond = row.to_condition_dict() if cond: out.append(cond) return out
[文档] def find_row_by_field(self, field_name: str) -> FilterRowWidget | None: """查找指定字段的筛选行。""" for row in self._rows: if row.field_combo.currentData() == field_name: return row return None
# ============================================================ # 快查框 # ============================================================
[文档] def set_quick_search_items(self, items: list[str]) -> None: """填充快查框候选项(editable + completer 模糊匹配)。""" if self._quick_search is None: return self._quick_search.set_items(items)
# ============================================================ # 预设选择器(preset 下拉,方案 B) # ============================================================
[文档] def set_quick_search_placeholder(self, text: str) -> None: """设置快查框占位提示(随 active preset 变化)。""" if self._quick_search is not None: self._quick_search.setPlaceholderText(text)
[文档] def set_presets(self, presets: list) -> None: """动态更新预设列表(视图切换时)。""" self._presets = list(presets or []) if self._preset_combo is not None: self._preset_combo.blockSignals(True) self._preset_combo.clear() self._preset_combo.addItems([p.get("name", "") for p in self._presets]) self._preset_combo.blockSignals(False)
[文档] def active_preset(self) -> dict | None: """返回当前选中的预设 dict(无预设选择器时返回 None)。""" if self._preset_combo is None or not self._presets: return None name = self._preset_combo.currentText() for p in self._presets: if p.get("name") == name: return p return None