This commit is contained in:
Your Name
2026-09-09 12:18:17 +08:00
parent 0cea43b027
commit 40319eea41
76 changed files with 4821 additions and 268 deletions
@@ -8,7 +8,7 @@ from html import escape
from math import ceil
from typing import Any
from PySide6.QtCore import QDateTime, QModelIndex, QRectF, QSize, Qt
from PySide6.QtCore import QDateTime, QModelIndex, QRectF, QSize, Qt
from PySide6.QtGui import (
QColor,
QFont,
@@ -19,7 +19,8 @@ from PySide6.QtGui import (
QTextDocument,
QTextOption,
)
from PySide6.QtWidgets import (
from PySide6.QtWidgets import (
QAbstractItemView,
QApplication,
QComboBox,
QDateTimeEdit,
@@ -563,7 +564,165 @@ def _create_time_cell(value: Any, _row: Any) -> str:
return format_record_time(raw)
class DoctorMultiSelect(QWidget):
class _DoctorOptionDelegate(QStyledItemDelegate):
"""Draw an explicit checked state independent of native item indicators."""
def sizeHint(self, option: Any, index: QModelIndex) -> QSize:
return QSize(220, 36)
def paint(self, painter: QPainter, option: Any, index: QModelIndex) -> None:
checked = index.data(Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Checked.value
painter.save()
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
rect = option.rect.adjusted(2, 1, -2, -1)
hovered = bool(option.state & QStyle.StateFlag.State_MouseOver)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(QColor("#EAF2FF" if checked else "#F3F7FD" if hovered else "#FFFFFF"))
painter.drawRoundedRect(rect, 5, 5)
box = QRectF(rect.left() + 10, rect.center().y() - 8, 16, 16)
painter.setPen(QPen(QColor("#1769E8" if checked else "#B8C7DA"), 1))
painter.setBrush(QColor("#1769E8" if checked else "#FFFFFF"))
painter.drawRoundedRect(box, 3, 3)
if checked:
pen = QPen(QColor("#FFFFFF"), 2)
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
painter.setPen(pen)
x, y = int(box.x()), int(box.y())
painter.drawLine(x + 4, y + 8, x + 7, y + 11)
painter.drawLine(x + 7, y + 11, x + 12, y + 5)
font = QFont(body_family())
font.setPixelSize(13)
painter.setFont(font)
painter.setPen(QColor("#273244"))
text_rect = rect.adjusted(38, 0, -54, 0)
label = painter.fontMetrics().elidedText(str(index.data() or ""), Qt.TextElideMode.ElideRight, text_rect.width())
painter.drawText(text_rect, Qt.AlignmentFlag.AlignVCenter, label)
if checked:
painter.setPen(QColor("#1769E8"))
painter.drawText(rect.adjusted(0, 0, -12, 0), Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter, "已选")
if option.state & QStyle.StateFlag.State_HasFocus:
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.setPen(QPen(QColor("#ADC8F2"), 1))
painter.drawRoundedRect(rect, 5, 5)
painter.restore()
def editorEvent(self, event: Any, model: Any, option: Any, index: QModelIndex) -> bool:
# The list handles whole-row toggles, independent of the native checkbox hit area.
return False
class _DoctorOptionsList(QListWidget):
@staticmethod
def _toggle(item: QListWidgetItem | None) -> None:
if item is not None and item.flags() & Qt.ItemFlag.ItemIsEnabled:
item.setCheckState(Qt.CheckState.Unchecked if item.checkState() == Qt.CheckState.Checked
else Qt.CheckState.Checked)
def mousePressEvent(self, event: Any) -> None:
if event.button() == Qt.MouseButton.LeftButton:
item = self.itemAt(event.position().toPoint())
self.setCurrentItem(item)
self._toggle(item)
event.accept()
return
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: Any) -> None:
if event.button() == Qt.MouseButton.LeftButton:
event.accept()
return
super().mouseReleaseEvent(event)
def keyPressEvent(self, event: Any) -> None:
if event.key() == Qt.Key.Key_Space:
if not event.isAutoRepeat():
self._toggle(self.currentItem())
event.accept()
return
super().keyPressEvent(event)
class _DoctorSelectionDialog(QDialog):
def __init__(self, options: Mapping[int, str], selected: set[int], parent: QWidget) -> None:
super().__init__(parent)
self.setObjectName("DoctorSelectionDialog")
self.setWindowTitle("选择医生")
self.resize(420, 500)
self.setStyleSheet("""
QDialog#DoctorSelectionDialog { background: #FFFFFF; color: #273244; }
QListWidget#DoctorOptionsList { background: #FFFFFF; border: 1px solid #DBE5F2; border-radius: 6px; padding: 3px; }
QLabel#DoctorSelectionCount { color: #1769E8; font-size: 13px; }
QDialog#DoctorSelectionDialog QLineEdit { border: 1px solid #DBE5F2; border-radius: 6px; }
QDialog#DoctorSelectionDialog QLineEdit:focus { border-color: #1769E8; }
QDialog#DoctorSelectionDialog QPushButton { background: #FFFFFF; color: #273244; border: 1px solid #DBE5F2; border-radius: 6px; }
QDialog#DoctorSelectionDialog QPushButton:hover { background: #F3F7FD; border-color: #ADC8F2; }
QDialog#DoctorSelectionDialog QPushButton:disabled { color: #8A97A9; }
QDialog#DoctorSelectionDialog QPushButton#ConfirmDoctorSelection { background: #1769E8; color: #FFFFFF; border-color: #1769E8; }
QDialog#DoctorSelectionDialog QPushButton#ConfirmDoctorSelection:hover { background: #125DCE; border-color: #125DCE; }
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(10)
self.search = QLineEdit(self)
self.search.setObjectName("DoctorOptionSearch")
self.search.setPlaceholderText("搜索医生姓名")
self.search.setClearButtonEnabled(True)
layout.addWidget(self.search)
summary = QHBoxLayout()
self.count_label = QLabel(self)
self.count_label.setObjectName("DoctorSelectionCount")
summary.addWidget(self.count_label, 1)
self.clear_button = QPushButton("清空选择", self)
self.clear_button.clicked.connect(self._clear)
summary.addWidget(self.clear_button)
layout.addLayout(summary)
self.listing = _DoctorOptionsList(self)
self.listing.setObjectName("DoctorOptionsList")
self.listing.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.listing.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.listing.setMouseTracking(True)
self.listing.setItemDelegate(_DoctorOptionDelegate(self.listing))
for doctor_id, name in sorted(options.items(), key=lambda item: item[1]):
item = QListWidgetItem(name)
item.setToolTip(name)
item.setData(Qt.ItemDataRole.UserRole, doctor_id)
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
item.setCheckState(Qt.CheckState.Checked if doctor_id in selected else Qt.CheckState.Unchecked)
self.listing.addItem(item)
layout.addWidget(self.listing, 1)
self.buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok, self)
self.buttons.button(QDialogButtonBox.StandardButton.Ok).setText("确定")
self.buttons.button(QDialogButtonBox.StandardButton.Ok).setObjectName("ConfirmDoctorSelection")
self.buttons.button(QDialogButtonBox.StandardButton.Cancel).setText("取消")
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
layout.addWidget(self.buttons)
self.listing.itemChanged.connect(self._update_summary)
self.search.textChanged.connect(self._filter)
self._update_summary()
def selected_ids(self) -> set[int]:
return {_int(self.listing.item(i).data(Qt.ItemDataRole.UserRole))
for i in range(self.listing.count())
if self.listing.item(i).checkState() == Qt.CheckState.Checked}
def _update_summary(self, *_args: Any) -> None:
count = len(self.selected_ids())
self.count_label.setText(f"已选 {count} 位医生")
self.clear_button.setEnabled(count > 0)
def _filter(self, text: str) -> None:
query = text.strip().casefold()
for i in range(self.listing.count()):
item = self.listing.item(i)
item.setHidden(query not in item.text().casefold())
def _clear(self) -> None:
for i in range(self.listing.count()):
self.listing.item(i).setCheckState(Qt.CheckState.Unchecked)
class DoctorMultiSelect(QWidget):
"""Compact checkable doctor selector fed by list rows/extend data."""
def __init__(self, parent: QWidget | None = None) -> None:
@@ -591,36 +750,14 @@ class DoctorMultiSelect(QWidget):
self._options[doctor_id] = name
self._update_text()
def _choose(self) -> None:
dialog = QDialog(self)
dialog.setWindowTitle("选择医生")
dialog.resize(360, 430)
layout = QVBoxLayout(dialog)
listing = QListWidget()
for doctor_id, name in sorted(self._options.items(), key=lambda item: item[1]):
item = QListWidgetItem(name)
item.setData(Qt.ItemDataRole.UserRole, doctor_id)
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
item.setCheckState(
Qt.CheckState.Checked if doctor_id in self._selected else Qt.CheckState.Unchecked
)
listing.addItem(item)
layout.addWidget(listing)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok
)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addWidget(buttons)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
self._selected = {
_int(listing.item(index).data(Qt.ItemDataRole.UserRole))
for index in range(listing.count())
if listing.item(index).checkState() == Qt.CheckState.Checked
}
self._selected.discard(0)
self._update_text()
def _choose(self) -> None:
dialog = _DoctorSelectionDialog(self._options, self._selected, self)
try:
if dialog.exec() == QDialog.DialogCode.Accepted:
self._selected = dialog.selected_ids() - {0}
self._update_text()
finally:
dialog.deleteLater()
def _update_text(self) -> None:
if not self._selected: