This commit is contained in:
Your Name
2026-08-22 08:51:35 +08:00
parent 6c444a4a04
commit c06d293424
69 changed files with 11431 additions and 1601 deletions
@@ -36,6 +36,7 @@ from ..dialogs.prescription import (
PrescriptionOrderDialog,
PrescriptionOrderListDialog,
)
from ..theme import crisp_pixmap
from ..widgets import (
BusinessPager,
EmptyState,
@@ -45,6 +46,7 @@ from ..widgets import (
TableColumn,
display_text,
first_value,
format_record_time,
friendly_error,
get_value,
has_permission,
@@ -105,6 +107,10 @@ PRESCRIPTIONS_PAGE_QSS = """
padding: 0; border-radius: 7px; background: #FFFFFF;
border: 1px solid #DCE3F5;
}
#PrescriptionsPage QPushButton[rowAction="true"][labeled="true"] {
min-width: 64px; max-width: 64px; padding: 0 9px;
color: #315CF4; font-weight: 600;
}
#PrescriptionsPage QPushButton[rowAction="true"]:hover {
background: #F3F5FF; border-color: #AAB7FF;
}
@@ -157,8 +163,7 @@ PRESCRIPTIONS_PAGE_QSS = """
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)
pixmap = crisp_pixmap(size)
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
pen = QPen(QColor(color), max(1.25, size / 11.5))
@@ -265,13 +270,20 @@ def _row_action_button(
*,
danger: bool = False,
enabled: bool = True,
label: str = "",
mutation: bool = False,
) -> QPushButton:
button = QPushButton(parent)
button.setProperty("rowAction", True)
button.setProperty("danger", danger)
button.setProperty("labeled", bool(label))
button.setProperty("mutationAction", mutation)
button.setProperty("rowAllowed", enabled)
button.setText(label)
button.setAccessibleName(tooltip)
button.setToolTip(tooltip)
button.setCursor(Qt.CursorShape.PointingHandCursor)
button.setFixedSize(64 if label else 28, 28)
button.setIcon(_painted_icon(kind, "#F34E64" if danger else "#4965F5", 15))
button.setIconSize(QSize(15, 15))
button.setEnabled(enabled)
@@ -427,6 +439,11 @@ def _doctor_cell(_value: Any, row: Any) -> str:
return str(first_value(row, "doctor_name", "creator_name", default="—"))
def _create_time_cell(value: Any, _row: Any) -> str:
raw = first_value(_row, "create_time_text", "create_time", default=value)
return format_record_time(raw)
class DoctorMultiSelect(QWidget):
"""Compact checkable doctor selector fed by list rows/extend data."""
@@ -682,6 +699,7 @@ class PrescriptionsPage(QWidget):
[
TableColumn("__selected__", "", 46, lambda _value, _row: ""),
TableColumn("sn", "处方编号", 174, _sn_cell),
TableColumn("__actions__", "操作", 150, lambda _value, _row: ""),
TableColumn("prescription_type", "处方类型", 96),
TableColumn("is_system_auto", "来源", 88, _source_cell),
TableColumn("patient_name", "患者信息", 190, _patient_cell),
@@ -689,8 +707,7 @@ class PrescriptionsPage(QWidget):
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: ""),
TableColumn("create_time", "创建时间", 180, _create_time_cell),
]
)
self.table.verticalHeader().setDefaultSectionSize(36)
@@ -840,7 +857,7 @@ class PrescriptionsPage(QWidget):
return
rows = page_items(result)
self.table.set_rows(rows)
self._decorate_rows(rows)
self._decorate_rows()
total = page_total(result, len(rows))
self.pager.update_state(requested_page, total)
self.count_badge.setText(f"共 {total} 条")
@@ -864,20 +881,27 @@ class PrescriptionsPage(QWidget):
self.table.selectRow(0)
self._selection_changed()
def _decorate_rows(self, rows: list[Any]) -> None:
def _decorate_rows(self) -> None:
"""Apply the reference table's tags, checkbox, avatar, and row actions."""
for row_index, row in enumerate(rows):
for row_index in range(self.table.rowCount()):
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)
if selector is None:
continue
# Read the row back from the visual table. SortableTable may have
# already reordered rows, so decorating the original result list
# can bind an action button to the wrong visible prescription.
row = selector.data(Qt.ItemDataRole.UserRole)
if row is None:
continue
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:
@@ -894,7 +918,7 @@ class PrescriptionsPage(QWidget):
)
self.table.setCellWidget(
row_index,
2,
3,
_style_row_host(
_cell_host(_tag_label(prescription_type, "accent", self.table.viewport())),
row_index,
@@ -903,11 +927,11 @@ class PrescriptionsPage(QWidget):
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)
audit_item = self.table.item(row_index, 6)
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)
row_index, 6, _style_row_host(_cell_host(audit), row_index)
)
doctor_name = _doctor_cell(None, row)
@@ -928,13 +952,13 @@ class PrescriptionsPage(QWidget):
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)
self.table.setCellWidget(row_index, 8, 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.setContentsMargins(3, 0, 3, 0)
actions.setSpacing(3)
actions.addStretch(1)
if has_permission(self.permissions, "cf.prescription/read"):
actions.addWidget(
@@ -957,6 +981,8 @@ class PrescriptionsPage(QWidget):
),
actions_host,
enabled=can_edit_or_delete(row),
label="编辑",
mutation=True,
)
)
if has_permission(self.permissions, "cf.prescription/del"):
@@ -970,23 +996,31 @@ class PrescriptionsPage(QWidget):
actions_host,
danger=True,
enabled=can_edit_or_delete(row),
mutation=True,
)
)
actions.addStretch(1)
self.table.setCellWidget(row_index, 10, actions_host)
self.table.setCellWidget(row_index, 2, actions_host)
self._sync_row_mutation_actions()
def _run_row_action(self, row: Any, callback: Callable[[], None]) -> None:
target_id = first_value(row, "id", "prescription_id", default=None)
target_id = _int(first_value(row, "id", "prescription_id", default=None), 0)
if target_id <= 0:
self.banner.show_message("处方 ID 无效,请刷新列表后重试。", "warning")
return
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)
candidate_id = _int(
first_value(candidate, "id", "prescription_id", default=None), 0
)
if candidate is row or (
target_id is not None and str(candidate_id) == str(target_id)
candidate_id > 0 and candidate_id == target_id
):
self.table.selectRow(row_index)
break
callback()
callback()
return
self.banner.show_message("该处方已不在当前列表,请刷新后重试。", "warning")
def _load_error(self, error: Exception, generation: int) -> None:
if generation == self._generation:
@@ -1013,8 +1047,17 @@ class PrescriptionsPage(QWidget):
self._mutation_pending = pending
self.add_button.setEnabled(not pending)
self.orders_button.setEnabled(not pending)
self._sync_row_mutation_actions()
self._selection_changed()
def _sync_row_mutation_actions(self) -> None:
for button in self.table.findChildren(QPushButton):
if not bool(button.property("mutationAction")):
continue
button.setEnabled(
not self._mutation_pending and bool(button.property("rowAllowed"))
)
def _selected(self) -> Any:
return self.table.current_data()
@@ -1054,6 +1097,10 @@ class PrescriptionsPage(QWidget):
) -> None:
if generation != self._detail_generation or prescription_id != self._detail_target:
return
detail_id = _int(first_value(detail, "id", "prescription_id"), 0)
if detail_id != prescription_id:
self.banner.show_message("处方详情与当前选择不一致,请刷新后重试。", "danger")
return
self.banner.clear()
callback(detail)
@@ -1148,6 +1195,7 @@ class PrescriptionsPage(QWidget):
row = self._selected()
if (
row is None
or self._mutation_pending
or not has_permission(self.permissions, "cf.prescription/edit")
or not can_edit_or_delete(row)
):
@@ -1159,6 +1207,9 @@ class PrescriptionsPage(QWidget):
)
def _open_editor(self, detail: Any) -> None:
if not can_edit_or_delete(detail):
self.banner.show_message("该处方状态已变化,当前不能编辑。", "warning")
return
dialog = PrescriptionEditorDialog(
self.repository,
detail,
@@ -1208,6 +1259,7 @@ class PrescriptionsPage(QWidget):
row = self._selected()
if (
row is None
or self._mutation_pending
or not has_permission(self.permissions, "cf.prescription/del")
or not can_edit_or_delete(row)
):
@@ -1222,6 +1274,9 @@ class PrescriptionsPage(QWidget):
if answer != QMessageBox.StandardButton.Yes:
return
prescription_id = _int(first_value(row, "id", "prescription_id"), 0)
if prescription_id <= 0:
self.banner.show_message("处方 ID 无效,请刷新列表后重试。", "warning")
return
self._set_mutation_pending(True)
run_async(
lambda: self.repository.delete_prescription(prescription_id),