This commit is contained in:
Your Name
2026-08-14 14:37:30 +08:00
parent 21790e35f4
commit 18c15d1262
117 changed files with 28157 additions and 8080 deletions
@@ -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")