更新
This commit is contained in:
@@ -6,8 +6,10 @@ from collections.abc import Callable, Mapping
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from PySide6.QtCore import QDateTime, Qt
|
||||
from PySide6.QtCore import QDateTime, QRectF, QSize, Qt, Signal
|
||||
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QPixmap
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QComboBox,
|
||||
QDateTimeEdit,
|
||||
QDialog,
|
||||
@@ -39,7 +41,6 @@ from ..widgets import (
|
||||
EmptyState,
|
||||
MessageBanner,
|
||||
PageHeader,
|
||||
Pager,
|
||||
SortableTable,
|
||||
TableColumn,
|
||||
display_text,
|
||||
@@ -54,6 +55,308 @@ from ..widgets import (
|
||||
show_toast,
|
||||
)
|
||||
|
||||
PRESCRIPTIONS_PAGE_QSS = """
|
||||
#PrescriptionsPage { background: #F8FAFF; }
|
||||
#PrescriptionsPage QWidget#PageHeader { min-height: 84px; max-height: 84px; }
|
||||
#PrescriptionsPage QWidget#PageHeader QLabel[role="breadcrumb"],
|
||||
#PrescriptionsPage QWidget#PageHeader QLabel[role="breadcrumbSeparator"],
|
||||
#PrescriptionsPage QWidget#PageHeader QLabel[role="breadcrumbCurrent"] {
|
||||
min-height: 16px; max-height: 16px;
|
||||
}
|
||||
#PrescriptionsPage QLabel[role="pageTitle"] {
|
||||
color: #15224A; font-size: 20px; font-weight: 700;
|
||||
}
|
||||
#PrescriptionsPage QWidget#PageHeader QLabel[role="muted"] {
|
||||
color: #7481A3; font-size: 12px; padding-top: 5px;
|
||||
}
|
||||
#PrescriptionsPage QFrame#PrescriptionFilterBar,
|
||||
#PrescriptionsPage QFrame#PrescriptionTableCard {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E2E7F4;
|
||||
border-radius: 13px;
|
||||
}
|
||||
#PrescriptionsPage QFrame#PrescriptionToolbar {
|
||||
background: #FFFFFF;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #E7EBF5;
|
||||
border-top-left-radius: 13px;
|
||||
border-top-right-radius: 13px;
|
||||
}
|
||||
#PrescriptionsPage QFrame#PrescriptionFilterBar QLineEdit,
|
||||
#PrescriptionsPage QFrame#PrescriptionFilterBar QComboBox,
|
||||
#PrescriptionsPage QFrame#PrescriptionFilterBar QDateTimeEdit {
|
||||
min-height: 34px; max-height: 34px; padding: 0 11px;
|
||||
border-radius: 8px; font-size: 12px;
|
||||
}
|
||||
#PrescriptionsPage QPushButton {
|
||||
min-height: 34px; max-height: 34px; padding: 0 14px;
|
||||
border-radius: 8px; font-size: 12px;
|
||||
}
|
||||
#PrescriptionsPage QFrame#PrescriptionToolbar QPushButton {
|
||||
min-height: 32px; max-height: 32px; padding: 0 13px;
|
||||
}
|
||||
#PrescriptionsPage QLabel#PrescriptionCountBadge {
|
||||
min-height: 22px; max-height: 22px; padding: 0 9px;
|
||||
color: #5265F6; background: #F0F2FF; border-radius: 11px;
|
||||
font-size: 11px; font-weight: 600;
|
||||
}
|
||||
#PrescriptionsPage QPushButton[rowAction="true"] {
|
||||
min-width: 28px; max-width: 28px; min-height: 28px; max-height: 28px;
|
||||
padding: 0; border-radius: 7px; background: #FFFFFF;
|
||||
border: 1px solid #DCE3F5;
|
||||
}
|
||||
#PrescriptionsPage QPushButton[rowAction="true"]:hover {
|
||||
background: #F3F5FF; border-color: #AAB7FF;
|
||||
}
|
||||
#PrescriptionsPage QPushButton[rowAction="true"][danger="true"] {
|
||||
background: #FFF9FA; border-color: #FFD9DE;
|
||||
}
|
||||
#PrescriptionsPage QPushButton[rowAction="true"][danger="true"]:hover {
|
||||
background: #FFF1F3; border-color: #FFACB8;
|
||||
}
|
||||
#PrescriptionsPage QTableWidget {
|
||||
border: 0; border-radius: 0; background: #FFFFFF;
|
||||
alternate-background-color: #FBFCFF; font-size: 12px;
|
||||
}
|
||||
#PrescriptionsPage QTableWidget::item {
|
||||
padding: 4px 8px; border-bottom: 1px solid #EDF0F7;
|
||||
}
|
||||
#PrescriptionsPage QTableWidget::item:selected {
|
||||
color: #26365F; background: #FCFDFF;
|
||||
}
|
||||
#PrescriptionsPage QHeaderView::section {
|
||||
min-height: 38px; max-height: 38px; padding: 0 8px;
|
||||
background: #F7F9FE; color: #7481A3;
|
||||
border: 0; border-bottom: 1px solid #E7EBF5;
|
||||
font-size: 12px; font-weight: 600;
|
||||
}
|
||||
#PrescriptionsPage QWidget#Pager { min-height: 42px; }
|
||||
#PrescriptionsPage QWidget#Pager QPushButton {
|
||||
min-width: 34px; max-height: 32px; min-height: 32px; padding: 0 10px;
|
||||
}
|
||||
#PrescriptionsPage QWidget#Pager QLabel#PagerActive {
|
||||
min-width: 48px; min-height: 30px; border-radius: 7px;
|
||||
background: #5265F6; color: #FFFFFF; font-weight: 700;
|
||||
}
|
||||
#PrescriptionsPage QWidget#BusinessPager QPushButton[pagerPage="true"] {
|
||||
min-width: 34px; max-width: 34px; min-height: 32px; max-height: 32px;
|
||||
padding: 0; background: #FFFFFF; color: #405074; border-color: #E2E7F4;
|
||||
}
|
||||
#PrescriptionsPage QWidget#BusinessPager QPushButton[pagerPage="true"][active="true"] {
|
||||
background: #5265F6; color: #FFFFFF; border-color: #5265F6;
|
||||
}
|
||||
#PrescriptionsPage QWidget#BusinessPager QComboBox {
|
||||
min-height: 32px; max-height: 32px; min-width: 92px; padding: 0 9px;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def _painted_icon(kind: str, color: str = "#5265F6", size: int = 16) -> QIcon:
|
||||
"""Return a crisp page-local icon without relying on emoji or icon fonts."""
|
||||
|
||||
pixmap = QPixmap(size, size)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
|
||||
pen = QPen(QColor(color), max(1.25, size / 11.5))
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
scale = size / 16.0
|
||||
|
||||
if kind == "eye":
|
||||
painter.drawEllipse(QRectF(6.0 * scale, 6.0 * scale, 4.0 * scale, 4.0 * scale))
|
||||
painter.drawArc(QRectF(1.8 * scale, 3.3 * scale, 12.4 * scale, 9.4 * scale), 18 * 16, 144 * 16)
|
||||
painter.drawArc(QRectF(1.8 * scale, 3.3 * scale, 12.4 * scale, 9.4 * scale), 198 * 16, 144 * 16)
|
||||
elif kind == "pencil":
|
||||
painter.drawLine(4.0 * scale, 12.0 * scale, 11.8 * scale, 4.2 * scale)
|
||||
painter.drawLine(5.7 * scale, 13.0 * scale, 13.0 * scale, 5.7 * scale)
|
||||
painter.drawLine(4.0 * scale, 12.0 * scale, 3.2 * scale, 14.0 * scale)
|
||||
painter.drawLine(3.2 * scale, 14.0 * scale, 5.7 * scale, 13.0 * scale)
|
||||
painter.drawLine(11.8 * scale, 4.2 * scale, 13.0 * scale, 5.7 * scale)
|
||||
elif kind == "trash":
|
||||
painter.drawRoundedRect(QRectF(4.4 * scale, 5.2 * scale, 7.2 * scale, 8.2 * scale), 1, 1)
|
||||
painter.drawLine(3.3 * scale, 4.1 * scale, 12.7 * scale, 4.1 * scale)
|
||||
painter.drawLine(6.2 * scale, 2.4 * scale, 9.8 * scale, 2.4 * scale)
|
||||
painter.drawLine(6.7 * scale, 7.2 * scale, 6.7 * scale, 11.2 * scale)
|
||||
painter.drawLine(9.3 * scale, 7.2 * scale, 9.3 * scale, 11.2 * scale)
|
||||
elif kind == "refresh":
|
||||
painter.drawArc(QRectF(2.5 * scale, 2.5 * scale, 11.0 * scale, 11.0 * scale), 35 * 16, 276 * 16)
|
||||
painter.drawLine(11.5 * scale, 2.7 * scale, 13.5 * scale, 3.0 * scale)
|
||||
painter.drawLine(11.5 * scale, 2.7 * scale, 12.1 * scale, 4.8 * scale)
|
||||
elif kind == "spark":
|
||||
painter.drawLine(8.0 * scale, 1.8 * scale, 8.0 * scale, 5.0 * scale)
|
||||
painter.drawLine(8.0 * scale, 11.0 * scale, 8.0 * scale, 14.2 * scale)
|
||||
painter.drawLine(1.8 * scale, 8.0 * scale, 5.0 * scale, 8.0 * scale)
|
||||
painter.drawLine(11.0 * scale, 8.0 * scale, 14.2 * scale, 8.0 * scale)
|
||||
painter.drawLine(4.0 * scale, 4.0 * scale, 6.0 * scale, 6.0 * scale)
|
||||
painter.drawLine(10.0 * scale, 10.0 * scale, 12.0 * scale, 12.0 * scale)
|
||||
painter.drawLine(12.0 * scale, 4.0 * scale, 10.0 * scale, 6.0 * scale)
|
||||
painter.drawLine(6.0 * scale, 10.0 * scale, 4.0 * scale, 12.0 * scale)
|
||||
painter.drawEllipse(QRectF(6.2 * scale, 6.2 * scale, 3.6 * scale, 3.6 * scale))
|
||||
elif kind == "plus":
|
||||
painter.drawLine(8.0 * scale, 3.2 * scale, 8.0 * scale, 12.8 * scale)
|
||||
painter.drawLine(3.2 * scale, 8.0 * scale, 12.8 * scale, 8.0 * scale)
|
||||
elif kind == "lock":
|
||||
painter.drawRoundedRect(QRectF(3.4 * scale, 7.0 * scale, 9.2 * scale, 7.0 * scale), 1.4, 1.4)
|
||||
painter.drawArc(QRectF(5.0 * scale, 2.4 * scale, 6.0 * scale, 8.0 * scale), 0, 180 * 16)
|
||||
elif kind == "checkbox":
|
||||
painter.drawRoundedRect(QRectF(3.2 * scale, 3.2 * scale, 9.6 * scale, 9.6 * scale), 1.6, 1.6)
|
||||
elif kind == "document":
|
||||
painter.drawRoundedRect(QRectF(3.4 * scale, 2.0 * scale, 9.2 * scale, 12.0 * scale), 1.2, 1.2)
|
||||
painter.drawLine(5.4 * scale, 6.0 * scale, 10.6 * scale, 6.0 * scale)
|
||||
painter.drawLine(5.4 * scale, 8.5 * scale, 10.6 * scale, 8.5 * scale)
|
||||
painter.drawLine(5.4 * scale, 11.0 * scale, 8.8 * scale, 11.0 * scale)
|
||||
painter.end()
|
||||
return QIcon(pixmap)
|
||||
|
||||
|
||||
def _tag_label(text: str, kind: str, parent: QWidget) -> QLabel:
|
||||
palettes = {
|
||||
"accent": ("#315CF4", "#EDF2FF"),
|
||||
"success": ("#16966F", "#E7F8F1"),
|
||||
"warning": ("#D98A16", "#FFF5DF"),
|
||||
"danger": ("#E84B5F", "#FFF0F2"),
|
||||
"neutral": ("#657397", "#F1F3F8"),
|
||||
}
|
||||
foreground, background = palettes.get(kind, palettes["neutral"])
|
||||
label = QLabel(text, parent)
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
label.setStyleSheet(
|
||||
f"color:{foreground};background:{background};border:0;border-radius:4px;"
|
||||
"padding:2px 7px;font-size:11px;font-weight:600;"
|
||||
)
|
||||
label.setSizePolicy(label.sizePolicy().horizontalPolicy(), label.sizePolicy().verticalPolicy())
|
||||
return label
|
||||
|
||||
|
||||
def _cell_host(widget: QWidget, *, alignment: Qt.AlignmentFlag = Qt.AlignmentFlag.AlignLeft) -> QWidget:
|
||||
host = QWidget(widget.parentWidget())
|
||||
layout = QHBoxLayout(host)
|
||||
layout.setContentsMargins(8, 0, 8, 0)
|
||||
layout.setSpacing(6)
|
||||
if alignment & Qt.AlignmentFlag.AlignHCenter:
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(widget)
|
||||
layout.addStretch(1)
|
||||
return host
|
||||
|
||||
|
||||
def _style_row_host(host: QWidget, row_index: int) -> QWidget:
|
||||
host.setObjectName("PrescriptionTableCellHost")
|
||||
host.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
|
||||
host.setStyleSheet(
|
||||
"QWidget#PrescriptionTableCellHost {"
|
||||
f"background:{'#FFFFFF' if row_index % 2 == 0 else '#FBFCFF'};border:0;"
|
||||
"}"
|
||||
)
|
||||
return host
|
||||
|
||||
|
||||
def _row_action_button(
|
||||
kind: str,
|
||||
tooltip: str,
|
||||
callback: Callable[[], None],
|
||||
parent: QWidget,
|
||||
*,
|
||||
danger: bool = False,
|
||||
enabled: bool = True,
|
||||
) -> QPushButton:
|
||||
button = QPushButton(parent)
|
||||
button.setProperty("rowAction", True)
|
||||
button.setProperty("danger", danger)
|
||||
button.setAccessibleName(tooltip)
|
||||
button.setToolTip(tooltip)
|
||||
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
button.setIcon(_painted_icon(kind, "#F34E64" if danger else "#4965F5", 15))
|
||||
button.setIconSize(QSize(15, 15))
|
||||
button.setEnabled(enabled)
|
||||
button.clicked.connect(callback)
|
||||
return button
|
||||
|
||||
|
||||
class BusinessPager(QWidget):
|
||||
"""Reference-style numbered pager while retaining the page's fixed size contract."""
|
||||
|
||||
page_changed = Signal(int)
|
||||
|
||||
def __init__(self, page_size: int = 15, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("BusinessPager")
|
||||
self.page = 1
|
||||
self.page_size = page_size
|
||||
self.total = 0
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 6, 0, 0)
|
||||
layout.setSpacing(7)
|
||||
self.summary = QLabel("共 0 条", self)
|
||||
self.summary.setProperty("role", "muted")
|
||||
layout.addWidget(self.summary)
|
||||
layout.addStretch(1)
|
||||
self.previous = QPushButton("‹", self)
|
||||
self.previous.setProperty("variant", "ghost")
|
||||
self.previous.clicked.connect(lambda: self._request(self.page - 1))
|
||||
layout.addWidget(self.previous)
|
||||
self.pages_host = QWidget(self)
|
||||
self.pages_layout = QHBoxLayout(self.pages_host)
|
||||
self.pages_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.pages_layout.setSpacing(5)
|
||||
layout.addWidget(self.pages_host)
|
||||
self.next = QPushButton("›", self)
|
||||
self.next.setProperty("variant", "ghost")
|
||||
self.next.clicked.connect(lambda: self._request(self.page + 1))
|
||||
layout.addWidget(self.next)
|
||||
self.size_combo = QComboBox(self)
|
||||
self.size_combo.addItem(f"{page_size} 条/页", page_size)
|
||||
layout.addWidget(self.size_combo)
|
||||
self.page_label: QPushButton | None = None
|
||||
self.update_state(1, 0)
|
||||
|
||||
@property
|
||||
def page_count(self) -> int:
|
||||
return max(1, (self.total + self.page_size - 1) // self.page_size)
|
||||
|
||||
def update_state(self, page: int, total: int) -> None:
|
||||
self.page = max(1, page)
|
||||
self.total = max(0, total)
|
||||
self.summary.setText(f"共 {self.total} 条")
|
||||
while self.pages_layout.count():
|
||||
item = self.pages_layout.takeAt(0)
|
||||
if item.widget() is not None:
|
||||
item.widget().deleteLater()
|
||||
count = self.page_count
|
||||
if count <= 4:
|
||||
pages: list[int | None] = list(range(1, count + 1))
|
||||
elif self.page <= 3:
|
||||
pages = [1, 2, 3, None, count]
|
||||
elif self.page >= count - 2:
|
||||
pages = [1, None, count - 2, count - 1, count]
|
||||
else:
|
||||
pages = [1, None, self.page, None, count]
|
||||
self.page_label = None
|
||||
for number in pages:
|
||||
if number is None:
|
||||
ellipsis = QLabel("…", self.pages_host)
|
||||
ellipsis.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
ellipsis.setFixedWidth(24)
|
||||
self.pages_layout.addWidget(ellipsis)
|
||||
continue
|
||||
button = QPushButton(str(number), self.pages_host)
|
||||
button.setProperty("pagerPage", True)
|
||||
button.setProperty("active", number == self.page)
|
||||
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
button.clicked.connect(lambda _checked=False, value=number: self._request(value))
|
||||
self.pages_layout.addWidget(button)
|
||||
if number == self.page:
|
||||
self.page_label = button
|
||||
self.previous.setEnabled(self.page > 1)
|
||||
self.next.setEnabled(self.page < count)
|
||||
|
||||
def _request(self, page: int) -> None:
|
||||
if 1 <= page <= self.page_count and page != self.page:
|
||||
self.page_changed.emit(page)
|
||||
|
||||
|
||||
def _int(value: Any, default: int = 0) -> int:
|
||||
try:
|
||||
@@ -77,11 +380,7 @@ def _row_mapping(value: Any) -> dict[str, Any]:
|
||||
if isinstance(raw, Mapping) and raw:
|
||||
return dict(raw)
|
||||
fields = getattr(value, "__dataclass_fields__", {})
|
||||
return {
|
||||
name: getattr(value, name, None)
|
||||
for name in fields
|
||||
if name != "raw"
|
||||
}
|
||||
return {name: getattr(value, name, None) for name in fields if name != "raw"}
|
||||
|
||||
|
||||
def merge_prescription_detail(row: Any, detail: Any) -> dict[str, Any]:
|
||||
@@ -166,18 +465,14 @@ def _order_warnings(row: Any) -> list[str]:
|
||||
|
||||
|
||||
def _sn_cell(_value: Any, row: Any) -> str:
|
||||
sn = first_value(row, "sn", "prescription_no", "id", default="—")
|
||||
prescription_id = first_value(row, "id", "prescription_id", default="—")
|
||||
warnings = _order_warnings(row)
|
||||
suffix = "\n" + "\n".join(warnings) if warnings else ""
|
||||
return f"{sn}\nID: {prescription_id}{suffix}"
|
||||
return str(first_value(row, "sn", "prescription_no", "id", default="—"))
|
||||
|
||||
|
||||
def _patient_cell(_value: Any, row: Any) -> str:
|
||||
gender = first_value(row, "gender", default=None)
|
||||
gender_text = "男" if gender in (1, "1") else "女" if gender in (0, "0") else "未知"
|
||||
age = first_value(row, "age", default="—")
|
||||
return f"{first_value(row, 'patient_name', default='—')}\n{gender_text} · {age}岁"
|
||||
return f"{first_value(row, 'patient_name', default='—')} · {gender_text} · {age}岁"
|
||||
|
||||
|
||||
def _source_cell(_value: Any, row: Any) -> str:
|
||||
@@ -208,10 +503,7 @@ def _void_cell(_value: Any, row: Any) -> str:
|
||||
|
||||
|
||||
def _doctor_cell(_value: Any, row: Any) -> str:
|
||||
return (
|
||||
f"{first_value(row, 'doctor_name', 'creator_name', default='—')}\n"
|
||||
f"{first_value(row, 'prescription_date', default='—')}"
|
||||
)
|
||||
return str(first_value(row, "doctor_name", "creator_name", default="—"))
|
||||
|
||||
|
||||
class DoctorMultiSelect(QWidget):
|
||||
@@ -292,6 +584,8 @@ class PrescriptionsPage(QWidget):
|
||||
parent: QWidget | None = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setObjectName("PrescriptionsPage")
|
||||
self.setStyleSheet(PRESCRIPTIONS_PAGE_QSS)
|
||||
self.repository = repository
|
||||
self.permissions = permissions
|
||||
self.current_user = current_user
|
||||
@@ -307,18 +601,27 @@ class PrescriptionsPage(QWidget):
|
||||
self._mutation_pending = False
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(24, 20, 24, 24)
|
||||
root.setSpacing(14)
|
||||
# Reference business screens place the heading 32 px below the 70 px
|
||||
# shell bar; keep that baseline shared with the library screen.
|
||||
root.setContentsMargins(24, 19, 24, 14)
|
||||
root.setSpacing(12)
|
||||
header = PageHeader(
|
||||
"已开处方",
|
||||
"管理处方审核、患者修正与履约订单;已通过且未作废的处方只允许查看。",
|
||||
)
|
||||
header.actions.setSpacing(16)
|
||||
self.orders_button = QPushButton("业务订单", header)
|
||||
self.orders_button.setMinimumWidth(86)
|
||||
self.orders_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.orders_button.setVisible(has_permission(permissions, "tcm.prescriptionOrder/lists"))
|
||||
self.orders_button.clicked.connect(lambda: self._open_orders())
|
||||
header.add_action(self.orders_button)
|
||||
self.add_button = QPushButton("+ 新增处方", header)
|
||||
self.add_button = QPushButton("新增处方", header)
|
||||
self.add_button.setMinimumWidth(122)
|
||||
self.add_button.setProperty("variant", "primary")
|
||||
self.add_button.setIcon(_painted_icon("plus", "#FFFFFF", 15))
|
||||
self.add_button.setIconSize(QSize(15, 15))
|
||||
self.add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_button.setVisible(has_permission(permissions, "cf.prescription/add"))
|
||||
self.add_button.clicked.connect(self._add_prescription)
|
||||
header.add_action(self.add_button)
|
||||
@@ -331,11 +634,11 @@ class PrescriptionsPage(QWidget):
|
||||
|
||||
def _build_filters(self) -> QWidget:
|
||||
frame = QFrame()
|
||||
frame.setObjectName("FilterBar")
|
||||
frame.setObjectName("PrescriptionFilterBar")
|
||||
grid = QGridLayout(frame)
|
||||
grid.setContentsMargins(14, 12, 14, 12)
|
||||
grid.setHorizontalSpacing(9)
|
||||
grid.setVerticalSpacing(8)
|
||||
grid.setContentsMargins(14, 17, 14, 17)
|
||||
grid.setHorizontalSpacing(18)
|
||||
grid.setVerticalSpacing(15)
|
||||
self.quick_date = QComboBox()
|
||||
self.quick_date.addItem("全部时间", "all")
|
||||
self.quick_date.addItem("今日", "today")
|
||||
@@ -366,7 +669,7 @@ class PrescriptionsPage(QWidget):
|
||||
self.source_filter.addItem("全部来源", "all")
|
||||
self.source_filter.addItem("手工", "manual")
|
||||
self.source_filter.addItem("空白处方", "system")
|
||||
grid.addWidget(self.source_filter, 0, 4)
|
||||
grid.addWidget(self.source_filter, 0, 4, 1, 2)
|
||||
self.sn_filter = QLineEdit()
|
||||
self.sn_filter.setPlaceholderText("处方编号")
|
||||
self.sn_filter.setClearButtonEnabled(True)
|
||||
@@ -382,34 +685,47 @@ class PrescriptionsPage(QWidget):
|
||||
current_name = str(first_value(self.current_user, "name", "real_name", default="")).strip()
|
||||
if current_id and current_name:
|
||||
self.doctor_filter.update_options([{"id": current_id, "name": current_name}])
|
||||
grid.addWidget(self.doctor_filter, 1, 2)
|
||||
query = QPushButton("查询")
|
||||
query.setProperty("variant", "secondary")
|
||||
query.clicked.connect(self._search)
|
||||
grid.addWidget(query, 1, 3)
|
||||
reset = QPushButton("重置")
|
||||
reset.setProperty("variant", "ghost")
|
||||
reset.clicked.connect(self._reset_filters)
|
||||
grid.addWidget(reset, 1, 4)
|
||||
grid.setColumnStretch(1, 1)
|
||||
grid.setColumnStretch(2, 1)
|
||||
grid.addWidget(self.doctor_filter, 1, 2, 1, 2)
|
||||
self.query_button = QPushButton("查询")
|
||||
self.query_button.setProperty("variant", "secondary")
|
||||
self.query_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.query_button.clicked.connect(self._search)
|
||||
grid.addWidget(self.query_button, 1, 4)
|
||||
self.reset_button = QPushButton("重置")
|
||||
self.reset_button.setProperty("variant", "ghost")
|
||||
self.reset_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.reset_button.clicked.connect(self._reset_filters)
|
||||
grid.addWidget(self.reset_button, 1, 5)
|
||||
grid.setColumnMinimumWidth(0, 218)
|
||||
grid.setColumnMinimumWidth(4, 92)
|
||||
grid.setColumnMinimumWidth(5, 92)
|
||||
grid.setColumnStretch(1, 383)
|
||||
grid.setColumnStretch(2, 320)
|
||||
grid.setColumnStretch(3, 207)
|
||||
return frame
|
||||
|
||||
def _build_table_card(self) -> QWidget:
|
||||
card = QFrame()
|
||||
card.setObjectName("Card")
|
||||
card.setObjectName("PrescriptionTableCard")
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(14, 13, 14, 13)
|
||||
layout.setSpacing(9)
|
||||
toolbar = QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
toolbar_host = QFrame(card)
|
||||
toolbar_host.setObjectName("PrescriptionToolbar")
|
||||
toolbar = QHBoxLayout(toolbar_host)
|
||||
toolbar.setContentsMargins(16, 11, 16, 11)
|
||||
toolbar.setSpacing(8)
|
||||
title = QLabel("处方列表")
|
||||
title.setProperty("role", "sectionTitle")
|
||||
toolbar.addWidget(title)
|
||||
self.count_badge = QLabel("共 0 条", toolbar_host)
|
||||
self.count_badge.setObjectName("PrescriptionCountBadge")
|
||||
toolbar.addWidget(self.count_badge)
|
||||
toolbar.addStretch(1)
|
||||
self.view_button = self._action_button("查看", "cf.prescription/read", self._view_selected)
|
||||
toolbar.addWidget(self.view_button)
|
||||
self.patch_button = self._action_button(
|
||||
"修正患者", "tcm.prescription/patchPatient", self._patch_selected
|
||||
"修改患者", "tcm.prescription/patchPatient", self._patch_selected
|
||||
)
|
||||
toolbar.addWidget(self.patch_button)
|
||||
self.create_order_button = self._action_button(
|
||||
@@ -428,30 +744,42 @@ class PrescriptionsPage(QWidget):
|
||||
toolbar.addWidget(self.delete_button)
|
||||
refresh = QPushButton("刷新")
|
||||
refresh.setProperty("variant", "ghost")
|
||||
refresh.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
refresh.setIcon(_painted_icon("refresh", "#5D6E96", 15))
|
||||
refresh.setIconSize(QSize(15, 15))
|
||||
refresh.clicked.connect(self.refresh)
|
||||
toolbar.addWidget(refresh)
|
||||
layout.addLayout(toolbar)
|
||||
layout.addWidget(toolbar_host)
|
||||
self.stack = QStackedWidget()
|
||||
table_host = QWidget()
|
||||
table_layout = QVBoxLayout(table_host)
|
||||
table_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.table = SortableTable(
|
||||
[
|
||||
TableColumn("sn", "处方编号", 190, _sn_cell),
|
||||
TableColumn("prescription_type", "处方类型", 95),
|
||||
TableColumn("is_system_auto", "来源", 90, _source_cell),
|
||||
TableColumn("patient_name", "患者信息", 120, _patient_cell),
|
||||
TableColumn("audit_status", "审核状态", 220, _audit_cell),
|
||||
TableColumn("void_status", "作废", 70, _void_cell),
|
||||
TableColumn("doctor_name", "医生信息", 130, _doctor_cell),
|
||||
TableColumn("assistant_name", "医助", 90),
|
||||
TableColumn("create_time", "创建时间", 145),
|
||||
TableColumn("__selected__", "", 46, lambda _value, _row: ""),
|
||||
TableColumn("sn", "处方编号", 174, _sn_cell),
|
||||
TableColumn("prescription_type", "处方类型", 96),
|
||||
TableColumn("is_system_auto", "来源", 88, _source_cell),
|
||||
TableColumn("patient_name", "患者信息", 190, _patient_cell),
|
||||
TableColumn("audit_status", "审核状态", 110, _audit_cell),
|
||||
TableColumn("void_status", "作废", 72, _void_cell),
|
||||
TableColumn("doctor_name", "医生信息", 180, _doctor_cell),
|
||||
TableColumn("assistant_name", "医助", 125),
|
||||
TableColumn("create_time", "创建时间", 180),
|
||||
TableColumn("__actions__", "操作", 150, lambda _value, _row: ""),
|
||||
]
|
||||
)
|
||||
self.table.verticalHeader().setDefaultSectionSize(36)
|
||||
self.table.horizontalHeader().setFixedHeight(38)
|
||||
self.table.setWordWrap(False)
|
||||
self.table.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.table.horizontalHeaderItem(0).setIcon(_painted_icon("checkbox", "#AEB9D4", 14))
|
||||
self.table.horizontalHeaderItem(0).setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.table.itemSelectionChanged.connect(self._selection_changed)
|
||||
self.table.itemDoubleClicked.connect(lambda _item: self._view_selected())
|
||||
table_layout.addWidget(self.table, 1)
|
||||
self.pager = Pager(self._page_size)
|
||||
self.pager = BusinessPager(self._page_size)
|
||||
self.pager.summary.hide()
|
||||
self.pager.page_changed.connect(self._change_page)
|
||||
table_layout.addWidget(self.pager)
|
||||
self.stack.addWidget(table_host)
|
||||
@@ -472,6 +800,17 @@ class PrescriptionsPage(QWidget):
|
||||
# Keep permission-driven initial visibility inside the page hierarchy;
|
||||
# otherwise Windows briefly exposes the button as its own window.
|
||||
button = QPushButton(text, self)
|
||||
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
icon_kind = {
|
||||
"查看": "eye",
|
||||
"编辑": "pencil",
|
||||
"删除": "trash",
|
||||
}.get(text)
|
||||
if icon_kind:
|
||||
button.setIcon(
|
||||
_painted_icon(icon_kind, "#F34E64" if danger else "#5265F6", 15)
|
||||
)
|
||||
button.setIconSize(QSize(15, 15))
|
||||
if danger:
|
||||
button.setProperty("variant", "danger")
|
||||
button.setVisible(has_permission(self.permissions, permission))
|
||||
@@ -578,7 +917,10 @@ class PrescriptionsPage(QWidget):
|
||||
return
|
||||
rows = page_items(result)
|
||||
self.table.set_rows(rows)
|
||||
self.pager.update_state(requested_page, page_total(result, len(rows)))
|
||||
self._decorate_rows(rows)
|
||||
total = page_total(result, len(rows))
|
||||
self.pager.update_state(requested_page, total)
|
||||
self.count_badge.setText(f"共 {total} 条")
|
||||
self.stack.setCurrentIndex(0 if rows else 1)
|
||||
doctor_rows = []
|
||||
for row in rows:
|
||||
@@ -599,6 +941,130 @@ class PrescriptionsPage(QWidget):
|
||||
self.table.selectRow(0)
|
||||
self._selection_changed()
|
||||
|
||||
def _decorate_rows(self, rows: list[Any]) -> None:
|
||||
"""Apply the reference table's tags, checkbox, avatar, and row actions."""
|
||||
|
||||
for row_index, row in enumerate(rows):
|
||||
selector = self.table.item(row_index, 0)
|
||||
if selector is not None:
|
||||
selector.setFlags(
|
||||
selector.flags()
|
||||
| Qt.ItemFlag.ItemIsUserCheckable
|
||||
| Qt.ItemFlag.ItemIsEnabled
|
||||
| Qt.ItemFlag.ItemIsSelectable
|
||||
)
|
||||
selector.setCheckState(Qt.CheckState.Unchecked)
|
||||
selector.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
sn_item = self.table.item(row_index, 1)
|
||||
if sn_item is not None:
|
||||
sn_item.setForeground(QColor("#315CF4"))
|
||||
font = sn_item.font()
|
||||
font.setWeight(QFont.Weight.DemiBold)
|
||||
sn_item.setFont(font)
|
||||
warnings = _order_warnings(row)
|
||||
if warnings:
|
||||
sn_item.setToolTip("\n".join(warnings))
|
||||
|
||||
prescription_type = display_text(
|
||||
first_value(row, "prescription_type", default="—")
|
||||
)
|
||||
self.table.setCellWidget(
|
||||
row_index,
|
||||
2,
|
||||
_style_row_host(
|
||||
_cell_host(_tag_label(prescription_type, "accent", self.table.viewport())),
|
||||
row_index,
|
||||
),
|
||||
)
|
||||
|
||||
audit_text, audit_kind = prescription_status(row)
|
||||
audit = _tag_label(audit_text, audit_kind, self.table.viewport())
|
||||
audit_item = self.table.item(row_index, 5)
|
||||
if audit_item is not None and "\n" in audit_item.text():
|
||||
audit.setToolTip(audit_item.text())
|
||||
self.table.setCellWidget(
|
||||
row_index, 5, _style_row_host(_cell_host(audit), row_index)
|
||||
)
|
||||
|
||||
doctor_name = _doctor_cell(None, row)
|
||||
doctor_host = QWidget(self.table.viewport())
|
||||
_style_row_host(doctor_host, row_index)
|
||||
doctor_layout = QHBoxLayout(doctor_host)
|
||||
doctor_layout.setContentsMargins(8, 0, 6, 0)
|
||||
doctor_layout.setSpacing(7)
|
||||
avatar = QLabel(doctor_name[:1] if doctor_name != "—" else "医", doctor_host)
|
||||
avatar.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
avatar.setFixedSize(20, 20)
|
||||
avatar.setStyleSheet(
|
||||
"color:#5365D9;background:#EEF1FF;border:1px solid #DBE1FF;"
|
||||
"border-radius:10px;font-size:10px;font-weight:700;"
|
||||
)
|
||||
doctor_layout.addWidget(avatar)
|
||||
doctor_label = QLabel(doctor_name, doctor_host)
|
||||
doctor_label.setStyleSheet("color:#31416A;background:transparent;border:0;")
|
||||
doctor_layout.addWidget(doctor_label)
|
||||
doctor_layout.addStretch(1)
|
||||
self.table.setCellWidget(row_index, 7, doctor_host)
|
||||
|
||||
actions_host = QWidget(self.table.viewport())
|
||||
_style_row_host(actions_host, row_index)
|
||||
actions = QHBoxLayout(actions_host)
|
||||
actions.setContentsMargins(7, 0, 7, 0)
|
||||
actions.setSpacing(7)
|
||||
actions.addStretch(1)
|
||||
if has_permission(self.permissions, "cf.prescription/read"):
|
||||
actions.addWidget(
|
||||
_row_action_button(
|
||||
"eye",
|
||||
"查看处方",
|
||||
lambda _checked=False, target=row: self._run_row_action(
|
||||
target, self._view_selected
|
||||
),
|
||||
actions_host,
|
||||
)
|
||||
)
|
||||
if has_permission(self.permissions, "cf.prescription/edit"):
|
||||
actions.addWidget(
|
||||
_row_action_button(
|
||||
"pencil",
|
||||
"编辑处方",
|
||||
lambda _checked=False, target=row: self._run_row_action(
|
||||
target, self._edit_selected
|
||||
),
|
||||
actions_host,
|
||||
enabled=can_edit_or_delete(row),
|
||||
)
|
||||
)
|
||||
if has_permission(self.permissions, "cf.prescription/del"):
|
||||
actions.addWidget(
|
||||
_row_action_button(
|
||||
"trash",
|
||||
"删除处方",
|
||||
lambda _checked=False, target=row: self._run_row_action(
|
||||
target, self._delete_selected
|
||||
),
|
||||
actions_host,
|
||||
danger=True,
|
||||
enabled=can_edit_or_delete(row),
|
||||
)
|
||||
)
|
||||
actions.addStretch(1)
|
||||
self.table.setCellWidget(row_index, 10, actions_host)
|
||||
|
||||
def _run_row_action(self, row: Any, callback: Callable[[], None]) -> None:
|
||||
target_id = first_value(row, "id", "prescription_id", default=None)
|
||||
for row_index in range(self.table.rowCount()):
|
||||
item = self.table.item(row_index, 0)
|
||||
candidate = item.data(Qt.ItemDataRole.UserRole) if item is not None else None
|
||||
candidate_id = first_value(candidate, "id", "prescription_id", default=None)
|
||||
if candidate is row or (
|
||||
target_id is not None and str(candidate_id) == str(target_id)
|
||||
):
|
||||
self.table.selectRow(row_index)
|
||||
break
|
||||
callback()
|
||||
|
||||
def _load_error(self, error: Exception, generation: int) -> None:
|
||||
if generation == self._generation:
|
||||
self.banner.show_message(friendly_error(error), "danger")
|
||||
|
||||
Reference in New Issue
Block a user