geng
This commit is contained in:
@@ -12,6 +12,7 @@ from typing import Any
|
||||
from PySide6.QtCore import QDate, QSize, Qt, QTimer, QUrl, Signal
|
||||
from PySide6.QtGui import QAction, QBrush, QColor, QDesktopServices, QPixmap
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QButtonGroup,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
@@ -34,6 +35,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from ..dialogs import DiagnosisDialog
|
||||
from ..dialogs.ai_consult import can_open_ai_consult, present_ai_consult
|
||||
from ..dialogs.prescription import (
|
||||
PrescriptionDetailDialog,
|
||||
PrescriptionEditorDialog,
|
||||
@@ -92,12 +94,12 @@ _SEMANTIC_COLORS = {
|
||||
|
||||
APPOINTMENTS_LIGHT_QSS = """
|
||||
#AppointmentsPage QWidget#PageHeader {
|
||||
min-height: 42px;
|
||||
max-height: 42px;
|
||||
min-height: 26px;
|
||||
max-height: 26px;
|
||||
}
|
||||
#AppointmentsPage QFrame#AppointmentFilterPanel {
|
||||
min-height: 92px;
|
||||
max-height: 92px;
|
||||
min-height: 80px;
|
||||
max-height: 80px;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #E2E7F4;
|
||||
border-radius: 10px;
|
||||
@@ -109,9 +111,9 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
border-radius: 10px;
|
||||
}
|
||||
#AppointmentsPage QPushButton[appointmentStat="true"] {
|
||||
min-height: 34px;
|
||||
max-height: 34px;
|
||||
padding: 0 10px;
|
||||
min-height: 30px;
|
||||
max-height: 30px;
|
||||
padding: 0 7px;
|
||||
color: #59698E;
|
||||
background-color: #F8F9FD;
|
||||
border: 0;
|
||||
@@ -141,8 +143,8 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
background-color: #FFF8ED;
|
||||
}
|
||||
#AppointmentsPage QLineEdit#AppointmentPatientSearch {
|
||||
min-height: 34px;
|
||||
max-height: 34px;
|
||||
min-height: 30px;
|
||||
max-height: 30px;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #DDE3F0;
|
||||
border-radius: 7px;
|
||||
@@ -157,10 +159,10 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
padding: 0 2px;
|
||||
}
|
||||
#AppointmentsPage QTableWidget#AppointmentTable::item {
|
||||
padding: 6px 8px;
|
||||
padding: 5px 7px;
|
||||
}
|
||||
#AppointmentsPage QTableWidget#AppointmentTable QHeaderView::section {
|
||||
min-height: 35px;
|
||||
min-height: 34px;
|
||||
background-color: #F8FAFF;
|
||||
}
|
||||
#AppointmentsPage QTableWidget#AppointmentTable {
|
||||
@@ -211,8 +213,9 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
#AppointmentsPage QPushButton[compactAction="true"] {
|
||||
min-height: 30px;
|
||||
padding: 0 10px;
|
||||
min-height: 28px;
|
||||
max-height: 28px;
|
||||
padding: 0 8px;
|
||||
border-radius: 7px;
|
||||
font-size: 11px;
|
||||
}
|
||||
@@ -238,7 +241,7 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
#AppointmentsPage QPushButton[variant="chip"]:focus { border-color: #8D9BFF; }
|
||||
#AppointmentsPage QTabBar#AppointmentStatusTabs::tab {
|
||||
min-width: 62px;
|
||||
min-height: 32px;
|
||||
min-height: 28px;
|
||||
padding: 0 7px;
|
||||
color: #7481A3;
|
||||
background-color: transparent;
|
||||
@@ -326,9 +329,9 @@ APPOINTMENTS_LIGHT_QSS = """
|
||||
#AppointmentsPage QPushButton[videoActionKind="success"] { color: #159C79; }
|
||||
#AppointmentsPage QPushButton[videoActionKind="warning"] { color: #C17A16; }
|
||||
#AppointmentsPage QPushButton[filterChoice="true"] {
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
padding: 0 13px;
|
||||
min-height: 28px;
|
||||
max-height: 28px;
|
||||
padding: 0 10px;
|
||||
color: #405074;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
@@ -436,14 +439,9 @@ def _diagnosis_id(row: Any) -> int:
|
||||
|
||||
|
||||
def _video_patient_id(row: Any) -> int:
|
||||
source = _as_int(first_value(row, "source_patient_id", default=0))
|
||||
if source > 0:
|
||||
return source
|
||||
diagnosis = _as_int(first_value(row, "diagnosis_id", default=0))
|
||||
patient = _as_int(first_value(row, "patient_id", default=0))
|
||||
if diagnosis > 0 and patient > 0 and diagnosis != patient:
|
||||
return patient
|
||||
return patient
|
||||
"""Return only the appointment row's explicitly separated patient owner."""
|
||||
|
||||
return _as_int(first_value(row, "source_patient_id", default=0))
|
||||
|
||||
|
||||
def _appointment_id(row: Any) -> int:
|
||||
@@ -604,6 +602,56 @@ def _flatten_departments(
|
||||
return result
|
||||
|
||||
|
||||
def _stable_signature_value(value: Any) -> Any:
|
||||
"""Return a deterministic, order-independent representation of API data."""
|
||||
|
||||
if value is None or isinstance(value, (bool, int, str)):
|
||||
return value
|
||||
if isinstance(value, float):
|
||||
return repr(value)
|
||||
if isinstance(value, Mapping):
|
||||
items = (
|
||||
(str(key), _stable_signature_value(item))
|
||||
for key, item in value.items()
|
||||
)
|
||||
return tuple(sorted(items, key=lambda pair: pair[0]))
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
||||
return tuple(_stable_signature_value(item) for item in value)
|
||||
if isinstance(value, (set, frozenset)):
|
||||
frozen = (_stable_signature_value(item) for item in value)
|
||||
return tuple(sorted(frozen, key=repr))
|
||||
dataclass_fields = getattr(type(value), "__dataclass_fields__", None)
|
||||
if isinstance(dataclass_fields, Mapping):
|
||||
return (
|
||||
type(value).__qualname__,
|
||||
tuple(
|
||||
(name, _stable_signature_value(getattr(value, name, None)))
|
||||
for name in dataclass_fields
|
||||
),
|
||||
)
|
||||
state = getattr(value, "__dict__", None)
|
||||
if isinstance(state, Mapping):
|
||||
return (type(value).__qualname__, _stable_signature_value(state))
|
||||
isoformat = getattr(value, "isoformat", None)
|
||||
if callable(isoformat):
|
||||
try:
|
||||
return (type(value).__qualname__, isoformat())
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return (type(value).__qualname__, str(value))
|
||||
|
||||
|
||||
def _appointment_result_signature(rows: Sequence[Any], total: int, extend: Any) -> tuple[Any, ...]:
|
||||
"""Build the stable render signature used to short-circuit unchanged polling."""
|
||||
|
||||
return (
|
||||
"appointments-render-v1",
|
||||
total,
|
||||
_stable_signature_value(rows),
|
||||
_stable_signature_value(extend),
|
||||
)
|
||||
|
||||
|
||||
class AppointmentsPage(QWidget):
|
||||
"""Desktop appointment list with status tabs, filters, call and prescription."""
|
||||
|
||||
@@ -639,6 +687,9 @@ class AppointmentsPage(QWidget):
|
||||
self._end_date = self._start_date
|
||||
self._status_counts: dict[int, int] = {1: 0, 2: 0, 3: 0, 4: 0}
|
||||
self._dict_labels: dict[str, dict[str, str]] = {}
|
||||
self._last_render_signature: tuple[Any, ...] | None = None
|
||||
self._responsive_narrow: bool | None = None
|
||||
self._video_panel_user_open = False
|
||||
self._can_plain_phone = _canonical_allowed(
|
||||
permissions, "tcm.diagnosis/phonePlain", default=False
|
||||
)
|
||||
@@ -646,20 +697,23 @@ class AppointmentsPage(QWidget):
|
||||
self._is_admin = _is_admin_user(current_user)
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(22, 7, 6, 8)
|
||||
root.setContentsMargins(18, 3, 6, 8)
|
||||
root.setSpacing(4)
|
||||
header = PageHeader("问诊列表")
|
||||
header.title_label.hide()
|
||||
header.subtitle_label.hide()
|
||||
root.addWidget(header)
|
||||
root.addWidget(self._build_filter_panel())
|
||||
self.header = PageHeader("问诊列表")
|
||||
self.header.title_label.hide()
|
||||
self.header.subtitle_label.hide()
|
||||
root.addWidget(self.header)
|
||||
self.filter_panel = self._build_filter_panel()
|
||||
root.addWidget(self.filter_panel)
|
||||
self.banner = MessageBanner()
|
||||
root.addWidget(self.banner)
|
||||
root.addWidget(self._build_content(), 1)
|
||||
self.content_host = self._build_content()
|
||||
root.addWidget(self.content_host, 1)
|
||||
|
||||
self.poll_timer = QTimer(self)
|
||||
self.poll_timer.setInterval(LIST_POLL_MS)
|
||||
self.poll_timer.timeout.connect(lambda: self.refresh(silent=True))
|
||||
self._apply_responsive_layout()
|
||||
|
||||
def _diagnosis_dialog(self) -> DiagnosisDialog:
|
||||
dialog = getattr(self, "_diagnosis_dialog_impl", None)
|
||||
@@ -673,76 +727,77 @@ class AppointmentsPage(QWidget):
|
||||
frame = QFrame()
|
||||
frame.setObjectName("AppointmentFilterPanel")
|
||||
layout = QVBoxLayout(frame)
|
||||
layout.setContentsMargins(4, 2, 12, 12)
|
||||
layout.setSpacing(10)
|
||||
layout.setContentsMargins(6, 4, 8, 6)
|
||||
layout.setSpacing(4)
|
||||
|
||||
date_row = QHBoxLayout()
|
||||
date_row.setSpacing(8)
|
||||
date_row.setSpacing(4)
|
||||
self.date_buttons: dict[str, QPushButton] = {}
|
||||
self._date_stat_labels: dict[str, str] = {}
|
||||
stat_widths = {
|
||||
"yesterday": 106,
|
||||
"day_before": 98,
|
||||
"today": 98,
|
||||
"tomorrow": 94,
|
||||
"day_after": 94,
|
||||
"": 110,
|
||||
}
|
||||
for source_label, preset in DATE_PRESETS:
|
||||
label = "全部" if preset == "" else source_label
|
||||
button = QPushButton(f"{label} 0")
|
||||
button.setCheckable(True)
|
||||
button.setProperty("appointmentStat", True)
|
||||
button.setFixedWidth(stat_widths[preset])
|
||||
button.setMinimumWidth(0)
|
||||
button.clicked.connect(
|
||||
lambda _checked=False, value=preset: self._set_date_preset(value)
|
||||
)
|
||||
self.date_buttons[preset] = button
|
||||
self._date_stat_labels[preset] = label
|
||||
date_row.addWidget(button)
|
||||
date_row.addWidget(button, 1)
|
||||
self.date_buttons["today"].setChecked(True)
|
||||
self.date_overflow_button = QPushButton("日期")
|
||||
self.date_overflow_button.setProperty("appointmentStat", True)
|
||||
date_menu = QMenu(self.date_overflow_button)
|
||||
for source_label, preset in DATE_PRESETS:
|
||||
action = date_menu.addAction(source_label if preset else "不限日期")
|
||||
action.triggered.connect(
|
||||
lambda _checked=False, value=preset: self._set_date_preset(value)
|
||||
)
|
||||
date_menu.addSeparator()
|
||||
date_menu.addAction("自定义日期").triggered.connect(self._open_custom_date)
|
||||
self.date_overflow_button.setMenu(date_menu)
|
||||
date_row.addWidget(self.date_overflow_button)
|
||||
|
||||
self.pending_stat_button = QPushButton("待预约 0")
|
||||
self.pending_stat_button.setProperty("appointmentStat", True)
|
||||
self.pending_stat_button.setProperty("appointmentStatKind", "pending")
|
||||
self.pending_stat_button.setFixedWidth(104)
|
||||
self.pending_stat_button.setMinimumWidth(0)
|
||||
self.pending_stat_button.clicked.connect(lambda: self._set_status_from_stat(1))
|
||||
date_row.addWidget(self.pending_stat_button)
|
||||
date_row.addWidget(self.pending_stat_button, 1)
|
||||
self.completed_stat_button = QPushButton("已完成 0")
|
||||
self.completed_stat_button.setProperty("appointmentStat", True)
|
||||
self.completed_stat_button.setProperty("appointmentStatKind", "success")
|
||||
self.completed_stat_button.setFixedWidth(104)
|
||||
self.completed_stat_button.setMinimumWidth(0)
|
||||
self.completed_stat_button.clicked.connect(lambda: self._set_status_from_stat(3))
|
||||
date_row.addWidget(self.completed_stat_button)
|
||||
date_row.addWidget(self.completed_stat_button, 1)
|
||||
self.unassigned_stat_button = QPushButton("待分配医助 0")
|
||||
self.unassigned_stat_button.setCheckable(True)
|
||||
self.unassigned_stat_button.setProperty("appointmentStat", True)
|
||||
self.unassigned_stat_button.setProperty("appointmentStatKind", "warning")
|
||||
self.unassigned_stat_button.setFixedWidth(130)
|
||||
self.unassigned_stat_button.setMinimumWidth(0)
|
||||
self.unassigned_stat_button.clicked.connect(self._toggle_unassigned_filter)
|
||||
date_row.addWidget(self.unassigned_stat_button)
|
||||
date_row.addStretch(1)
|
||||
date_row.addWidget(self.unassigned_stat_button, 1)
|
||||
|
||||
self.patient_input = QLineEdit()
|
||||
self.patient_input.setObjectName("AppointmentPatientSearch")
|
||||
self.patient_input.setPlaceholderText("患者姓名 / 手机号")
|
||||
self.patient_input.setClearButtonEnabled(True)
|
||||
self.patient_input.setFixedWidth(204)
|
||||
self.patient_input.returnPressed.connect(self._search)
|
||||
date_row.addWidget(self.patient_input)
|
||||
date_row.addWidget(self.patient_input, 2)
|
||||
|
||||
search = QPushButton("查询")
|
||||
search.setProperty("variant", "primary")
|
||||
search.setFixedWidth(62)
|
||||
search.clicked.connect(self._search)
|
||||
date_row.addWidget(search)
|
||||
layout.addLayout(date_row)
|
||||
|
||||
filter_row = QHBoxLayout()
|
||||
filter_row.setSpacing(7)
|
||||
status_label = QLabel("挂号状态:")
|
||||
status_label.setObjectName("FilterRowLabel")
|
||||
filter_row.addWidget(status_label)
|
||||
filter_row.setSpacing(5)
|
||||
self.status_filter_label = QLabel("挂号状态:")
|
||||
self.status_filter_label.setObjectName("FilterRowLabel")
|
||||
filter_row.addWidget(self.status_filter_label)
|
||||
self.tab_bar = QTabBar()
|
||||
self.tab_bar.setObjectName("AppointmentStatusTabs")
|
||||
self._tab_indexes: dict[str | int, int] = {}
|
||||
@@ -753,13 +808,15 @@ class AppointmentsPage(QWidget):
|
||||
self.tab_bar.currentChanged.connect(self._tab_changed)
|
||||
filter_row.addWidget(self.tab_bar)
|
||||
|
||||
self.filter_dividers: list[QLabel] = []
|
||||
divider = QLabel("│")
|
||||
divider.setObjectName("FilterDivider")
|
||||
self.filter_dividers.append(divider)
|
||||
filter_row.addWidget(divider)
|
||||
|
||||
confirm_label = QLabel("确认状态:")
|
||||
confirm_label.setObjectName("FilterRowLabel")
|
||||
filter_row.addWidget(confirm_label)
|
||||
self.confirm_filter_label = QLabel("确认状态:")
|
||||
self.confirm_filter_label.setObjectName("FilterRowLabel")
|
||||
filter_row.addWidget(self.confirm_filter_label)
|
||||
self.confirmed_filter = QComboBox(frame)
|
||||
self.confirmed_filter.addItem("全部", "")
|
||||
self.confirmed_filter.addItem("已确认", "1")
|
||||
@@ -783,6 +840,7 @@ class AppointmentsPage(QWidget):
|
||||
|
||||
divider = QLabel("│")
|
||||
divider.setObjectName("FilterDivider")
|
||||
self.filter_dividers.append(divider)
|
||||
filter_row.addWidget(divider)
|
||||
|
||||
self.more_filters_button = QPushButton("更多筛选")
|
||||
@@ -825,26 +883,32 @@ class AppointmentsPage(QWidget):
|
||||
|
||||
def _build_content(self) -> QWidget:
|
||||
host = QWidget()
|
||||
host.setMinimumHeight(0)
|
||||
layout = QHBoxLayout(host)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(4)
|
||||
layout.addWidget(self._build_table_card(), 3)
|
||||
layout.addWidget(self._build_video_panel(), 1)
|
||||
self.content_layout = layout
|
||||
self.table_card = self._build_table_card()
|
||||
self.video_panel = self._build_video_panel()
|
||||
layout.addWidget(self.table_card, 3)
|
||||
layout.addWidget(self.video_panel, 1)
|
||||
return host
|
||||
|
||||
def _build_video_panel(self) -> QWidget:
|
||||
panel = QFrame()
|
||||
panel.setObjectName("VideoConsultPanel")
|
||||
panel.setFixedWidth(420)
|
||||
panel.setMinimumWidth(0)
|
||||
panel.setMaximumWidth(420)
|
||||
layout = QVBoxLayout(panel)
|
||||
layout.setContentsMargins(18, 17, 18, 10)
|
||||
layout.setSpacing(10)
|
||||
layout.setContentsMargins(14, 12, 14, 8)
|
||||
layout.setSpacing(8)
|
||||
title = QLabel("视频问诊")
|
||||
title.setObjectName("VideoConsultTitle")
|
||||
layout.addWidget(title)
|
||||
self.video_list = QListWidget()
|
||||
self.video_list.setObjectName("VideoConsultList")
|
||||
self.video_list.setSpacing(0)
|
||||
self.video_list.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.video_list.currentRowChanged.connect(self._video_row_selected)
|
||||
self.video_list.itemDoubleClicked.connect(lambda _item: self._open_detail())
|
||||
layout.addWidget(self.video_list, 1)
|
||||
@@ -857,9 +921,10 @@ class AppointmentsPage(QWidget):
|
||||
def _build_table_card(self) -> QWidget:
|
||||
card = QFrame()
|
||||
card.setObjectName("AppointmentMainCard")
|
||||
card.setMinimumHeight(0)
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(4, 18, 10, 10)
|
||||
layout.setSpacing(14)
|
||||
layout.setContentsMargins(4, 8, 8, 8)
|
||||
layout.setSpacing(8)
|
||||
|
||||
compatibility_host = QWidget(card)
|
||||
compatibility_host.hide()
|
||||
@@ -904,7 +969,7 @@ class AppointmentsPage(QWidget):
|
||||
widget.setProperty("compactAction", True)
|
||||
|
||||
actions = QHBoxLayout()
|
||||
actions.setSpacing(9)
|
||||
actions.setSpacing(6)
|
||||
self.toolbar_edit_button = QPushButton("编辑患者", card)
|
||||
self.toolbar_edit_button.setProperty("variant", "primary")
|
||||
self.toolbar_edit_button.setProperty("compactAction", True)
|
||||
@@ -923,6 +988,13 @@ class AppointmentsPage(QWidget):
|
||||
self.toolbar_qr_button.setEnabled(False)
|
||||
self.toolbar_qr_button.clicked.connect(self._request_video_qr)
|
||||
actions.addWidget(self.toolbar_qr_button)
|
||||
self.toolbar_ai_consult_button = QPushButton("AI 分析", card)
|
||||
self.toolbar_ai_consult_button.setProperty("variant", "secondary")
|
||||
self.toolbar_ai_consult_button.setProperty("compactAction", True)
|
||||
self.toolbar_ai_consult_button.setVisible(can_open_ai_consult(self.permissions))
|
||||
self.toolbar_ai_consult_button.setEnabled(False)
|
||||
self.toolbar_ai_consult_button.clicked.connect(self._open_ai_consult)
|
||||
actions.addWidget(self.toolbar_ai_consult_button)
|
||||
self.toolbar_cancel_button = QPushButton("取消挂号", card)
|
||||
self.toolbar_cancel_button.setProperty("variant", "danger")
|
||||
self.toolbar_cancel_button.setProperty("compactAction", True)
|
||||
@@ -932,9 +1004,16 @@ class AppointmentsPage(QWidget):
|
||||
self.toolbar_cancel_button.setEnabled(False)
|
||||
self.toolbar_cancel_button.clicked.connect(self._cancel_selected)
|
||||
actions.addWidget(self.toolbar_cancel_button)
|
||||
self.video_panel_button = QPushButton("视频问诊", card)
|
||||
self.video_panel_button.setCheckable(True)
|
||||
self.video_panel_button.setProperty("variant", "ghost")
|
||||
self.video_panel_button.setProperty("compactAction", True)
|
||||
self.video_panel_button.clicked.connect(self._toggle_video_panel)
|
||||
actions.addWidget(self.video_panel_button)
|
||||
actions.addStretch(1)
|
||||
refresh = QPushButton("刷新", card)
|
||||
refresh.setProperty("variant", "ghost")
|
||||
refresh.setProperty("compactAction", True)
|
||||
refresh.clicked.connect(lambda: self.refresh())
|
||||
actions.addWidget(refresh)
|
||||
layout.addLayout(actions)
|
||||
@@ -958,17 +1037,56 @@ class AppointmentsPage(QWidget):
|
||||
]
|
||||
)
|
||||
self.table.setObjectName("AppointmentTable")
|
||||
self.table.setMinimumHeight(0)
|
||||
self.table.setWordWrap(True)
|
||||
self.table.horizontalHeader().setFixedHeight(38)
|
||||
self.table.verticalHeader().setDefaultSectionSize(66)
|
||||
self.table.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.table.horizontalHeader().setFixedHeight(34)
|
||||
self.table.verticalHeader().setDefaultSectionSize(60)
|
||||
self.table.itemSelectionChanged.connect(self._selection_changed)
|
||||
self.table.itemDoubleClicked.connect(lambda _item: self._open_detail())
|
||||
layout.addWidget(self.table, 1)
|
||||
self.pager = Pager(self._page_size)
|
||||
self.pager.setMaximumHeight(38)
|
||||
self.pager.page_changed.connect(self._page_changed)
|
||||
layout.addWidget(self.pager)
|
||||
return card
|
||||
|
||||
def _toggle_video_panel(self, checked: bool) -> None:
|
||||
self._video_panel_user_open = checked
|
||||
self._apply_responsive_layout()
|
||||
|
||||
def _apply_responsive_layout(self) -> None:
|
||||
"""Keep the table usable while retaining a video-list entry at low widths."""
|
||||
|
||||
panel = getattr(self, "video_panel", None)
|
||||
button = getattr(self, "video_panel_button", None)
|
||||
if not isinstance(panel, QWidget) or not isinstance(button, QPushButton):
|
||||
return
|
||||
narrow = self.width() < 1120
|
||||
for preset in ("yesterday", "day_before", "tomorrow", "day_after"):
|
||||
self.date_buttons[preset].setVisible(not narrow)
|
||||
self.date_overflow_button.setVisible(narrow)
|
||||
self.status_filter_label.setVisible(not narrow)
|
||||
self.confirm_filter_label.setVisible(not narrow)
|
||||
if narrow:
|
||||
if self._responsive_narrow is not True:
|
||||
self._video_panel_user_open = False
|
||||
button.show()
|
||||
button.setChecked(self._video_panel_user_open)
|
||||
panel.setVisible(self._video_panel_user_open)
|
||||
if self._video_panel_user_open:
|
||||
panel.setFixedWidth(max(250, min(300, round(self.width() * 0.32))))
|
||||
else:
|
||||
button.hide()
|
||||
button.setChecked(False)
|
||||
panel.show()
|
||||
panel.setFixedWidth(max(300, min(420, round(self.width() * 0.30))))
|
||||
self._responsive_narrow = narrow
|
||||
|
||||
def resizeEvent(self, event: Any) -> None:
|
||||
super().resizeEvent(event)
|
||||
self._apply_responsive_layout()
|
||||
|
||||
def _sync_video_list(self, rows: Sequence[Any]) -> None:
|
||||
selected_id = (
|
||||
_appointment_id(self.video_list.currentItem().data(Qt.ItemDataRole.UserRole))
|
||||
@@ -1002,6 +1120,7 @@ class AppointmentsPage(QWidget):
|
||||
self.video_list.setCurrentRow(row_to_select)
|
||||
else:
|
||||
self.video_list.setCurrentRow(-1)
|
||||
self.video_panel_button.setText(f"视频问诊 {len(rows)}")
|
||||
|
||||
def _build_video_patient_card(self, row: Any) -> QWidget:
|
||||
card = QFrame()
|
||||
@@ -1066,6 +1185,8 @@ class AppointmentsPage(QWidget):
|
||||
)
|
||||
add_action("查看", self._open_detail)
|
||||
add_action("诊单", self._edit_patient, "tcm.diagnosis/edit")
|
||||
if can_open_ai_consult(self.permissions):
|
||||
add_action("AI 分析", self._open_ai_consult)
|
||||
add_action("开方", self._open_prescription, "tcm.diagnosis/kaifang")
|
||||
add_action("二维码", self._request_video_qr, "tcm.diagnosis/videoQr", "success")
|
||||
more = QPushButton("更多", card)
|
||||
@@ -1109,6 +1230,8 @@ class AppointmentsPage(QWidget):
|
||||
|
||||
add_action("完成问诊", self._complete_selected, "doctor.appointment/complete")
|
||||
add_action("查看病历", self._view_case, "tcm.diagnosis/kaifang")
|
||||
if can_open_ai_consult(self.permissions):
|
||||
add_action("AI 分析", self._open_ai_consult)
|
||||
if can_open_diagnosis_ai_report(self.permissions):
|
||||
add_action("AI 报告", self._open_ai_report)
|
||||
add_action("取消挂号", self._cancel_selected, "doctor.appointment/cancel")
|
||||
@@ -1193,6 +1316,13 @@ class AppointmentsPage(QWidget):
|
||||
self._date_preset = preset
|
||||
for key, button in self.date_buttons.items():
|
||||
button.setChecked(key == preset)
|
||||
overflow_labels = {
|
||||
"yesterday": "昨天",
|
||||
"day_before": "前天",
|
||||
"tomorrow": "明天",
|
||||
"day_after": "后天",
|
||||
}
|
||||
self.date_overflow_button.setText(overflow_labels.get(preset, "日期"))
|
||||
target = _offset_date(preset)
|
||||
if target is None:
|
||||
self._start_date = ""
|
||||
@@ -1248,6 +1378,7 @@ class AppointmentsPage(QWidget):
|
||||
for button in self.date_buttons.values():
|
||||
button.setChecked(False)
|
||||
self.date_buttons[""].setChecked(True)
|
||||
self.date_overflow_button.setText("自定义")
|
||||
self._start_date = start_date
|
||||
self._end_date = end_date
|
||||
self._page = 1
|
||||
@@ -1332,6 +1463,16 @@ class AppointmentsPage(QWidget):
|
||||
rows = page_items(result)
|
||||
total = page_total(result)
|
||||
extend = get_value(result, "extend", {}) or {}
|
||||
signature = (
|
||||
self._page,
|
||||
_stable_signature_value(self._query_filters()),
|
||||
*_appointment_result_signature(rows, total, extend),
|
||||
)
|
||||
if signature == self._last_render_signature:
|
||||
if not silent:
|
||||
self.banner.clear()
|
||||
return
|
||||
self._last_render_signature = signature
|
||||
status_count = get_value(extend, "status_count", {}) or {}
|
||||
if isinstance(status_count, Mapping):
|
||||
for key in (1, 2, 3, 4):
|
||||
@@ -1507,7 +1648,7 @@ class AppointmentsPage(QWidget):
|
||||
default=1,
|
||||
)
|
||||
height = line_count * line_height + TABLE_CELL_VERTICAL_PADDING
|
||||
self.table.setRowHeight(row_index, max(66, height))
|
||||
self.table.setRowHeight(row_index, max(60, min(66, height)))
|
||||
|
||||
def _load_error(self, error: Exception, generation: int, silent: bool) -> None:
|
||||
if generation != self._generation:
|
||||
@@ -1551,6 +1692,9 @@ class AppointmentsPage(QWidget):
|
||||
self.prescription_button.setText(prescription_action_label(row) if has_row else "开方")
|
||||
self.case_button.setEnabled(has_row and _appointment_id(row) > 0)
|
||||
self.ai_button.setEnabled(has_row and _diagnosis_id(row) > 0)
|
||||
self.toolbar_ai_consult_button.setEnabled(
|
||||
has_row and can_open_ai_consult(self.permissions) and _diagnosis_id(row) > 0
|
||||
)
|
||||
self.cancel_button.setEnabled(has_row and status == 1)
|
||||
self.toolbar_edit_button.setEnabled(has_row and _diagnosis_id(row) > 0)
|
||||
self.toolbar_qr_button.setEnabled(
|
||||
@@ -1592,6 +1736,19 @@ class AppointmentsPage(QWidget):
|
||||
)
|
||||
present_diagnosis_ai_report(self.repository, self.permissions, self, payload)
|
||||
|
||||
def _open_ai_consult(self) -> None:
|
||||
row = self._current_row()
|
||||
diagnosis_id = _diagnosis_id(row)
|
||||
present_ai_consult(
|
||||
self.repository,
|
||||
self.permissions,
|
||||
self,
|
||||
diagnosis_id=diagnosis_id,
|
||||
patient_id=_video_patient_id(row),
|
||||
seed=row,
|
||||
source_title="问诊列表",
|
||||
)
|
||||
|
||||
def _request_video(self) -> None:
|
||||
if not _canonical_allowed(
|
||||
self.permissions, "doctor.appointment/prescription", default=False
|
||||
@@ -1640,7 +1797,7 @@ class AppointmentsPage(QWidget):
|
||||
if row is None or _status_value(row) == 3:
|
||||
return
|
||||
diagnosis_id = _diagnosis_id(row)
|
||||
patient_id = _video_patient_id(row) or _as_int(first_value(row, "patient_id", default=0))
|
||||
patient_id = _video_patient_id(row)
|
||||
doctor_id = _as_int(first_value(row, "doctor_id", default=0))
|
||||
share_user_id = _as_int(first_value(self.current_user, "id", default=0))
|
||||
if diagnosis_id <= 0 or patient_id <= 0 or doctor_id <= 0 or share_user_id <= 0:
|
||||
|
||||
Reference in New Issue
Block a user