This commit is contained in:
Your Name
2026-08-31 15:17:34 +08:00
parent ed48f8be31
commit 456dd667df
439 changed files with 5720 additions and 422 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
__all__ = ["DEBUG_MODE", "ONLINE_API_BASE_URL", "__version__"]
# Single source of truth for runtime, package, installer, and executable versions.
__version__ = "1.3.0"
__version__ = "1.2.0"
# 调试模式开启时,登录页显示“演示模式”和“服务器设置”。
# 正式发布请保持 False;此时程序只使用下面配置的线上域名。
@@ -714,7 +714,10 @@ def _spawn_inno_setup_applier(
if exit_code is not None:
raise OSError(f"更新助手启动后提前退出(代码 {exit_code})")
time.sleep(0.05)
process.terminate()
try: # noqa: SIM105 - best-effort cleanup before reporting the handshake timeout
process.terminate()
except OSError:
pass
raise OSError(f"更新助手启动超时,未生成就绪标记:{ready_file}")
@@ -7,9 +7,10 @@ object names and dynamic properties so adjacent pages keep their own styling.
from __future__ import annotations
import weakref
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
import weakref
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from math import ceil
from typing import Any
from PySide6.QtCore import (
@@ -1257,7 +1258,7 @@ class DiagnosisLineEdit(QLineEdit):
self.setText(text)
class DiagnosisTextEdit(QPlainTextEdit):
class DiagnosisTextEdit(QPlainTextEdit):
"""Multiline editor using the same scoped field state as line inputs."""
def __init__(self, rows: int = 3, parent: QWidget | None = None) -> None:
@@ -1273,11 +1274,113 @@ class DiagnosisTextEdit(QPlainTextEdit):
# QPlainTextEdit defaults to ~80 columns; that blows past the drawer width.
return QSize(48, self._hint_height)
def sizeHint(self) -> QSize: # noqa: N802 - Qt virtual
return QSize(160, self._hint_height)
class DiagnosisComboBox(QComboBox):
def sizeHint(self) -> QSize: # noqa: N802 - Qt virtual
return QSize(160, self._hint_height)
class ExpandableDiagnosisTextEdit(DiagnosisTextEdit):
"""An inline long-text editor without an inner scrollbar when expanded."""
def __init__(self, rows: int = 3, parent: QWidget | None = None) -> None:
super().__init__(rows, parent)
self._collapsed_height = self._hint_height
self._caption = "内容"
self._expand_host: QWidget | None = None
self.expand_button = QPushButton("展开全部", self)
self.expand_button.setObjectName("DiagnosisTextExpandButton")
self.expand_button.setProperty("variant", "link")
self.expand_button.setCheckable(True)
self.expand_button.setAutoDefault(False)
self.expand_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.expand_button.setToolTip("展开文本框,查看和编辑全部内容")
self.expand_button.hide()
self.expand_button.toggled.connect(self._toggle_expanded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self._height_sync = QTimer(self)
self._height_sync.setSingleShot(True)
self._height_sync.setInterval(0)
self._height_sync.timeout.connect(self._sync_content_height)
self.textChanged.connect(self._height_sync.start)
self.document().documentLayout().documentSizeChanged.connect(
lambda _size: self._height_sync.start()
)
def wrap_with_expander(self, caption: str) -> QWidget:
"""Keep the original plain-text editor as the form's save/readonly target."""
if self._expand_host is None:
self._caption = caption
self.setAccessibleName(caption)
self.expand_button.setAccessibleName(f"展开{caption}全部内容")
self._expand_host = QWidget()
self._expand_host.setMinimumWidth(0)
self._expand_host.setFocusProxy(self)
layout = QVBoxLayout(self._expand_host)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(2)
layout.addWidget(self)
layout.addWidget(self.expand_button, 0, Qt.AlignmentFlag.AlignRight)
return self._expand_host
def setPlainText(self, text: str) -> None: # noqa: N802 - Qt API
# A newly loaded record starts compact; typing/paste/undo keep its state.
self.expand_button.setChecked(False)
super().setPlainText(text)
def _toggle_expanded(self, expanded: bool) -> None:
self.expand_button.setText("收起" if expanded else "展开全部")
self.expand_button.setAccessibleName(
f"收起{self._caption}" if expanded else f"展开{self._caption}全部内容"
)
self.expand_button.setToolTip(
"收起为紧凑文本框,内容不会丢失" if expanded else "展开文本框,查看和编辑全部内容"
)
self.setVerticalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOff
if expanded
else Qt.ScrollBarPolicy.ScrollBarAsNeeded
)
self.verticalScrollBar().setValue(0)
self._height_sync.start()
def _sync_content_height(self) -> None:
expanded = self.expand_button.isChecked()
document = self.document()
document_layout = document.documentLayout()
# QPlainTextDocumentLayout.documentSize().height() counts lines, not
# pixels. Measure the actual wrapped blocks, including offscreen ones.
height = self.height() - self.viewport().height() + 2 * document.documentMargin()
block = document.begin()
while block.isValid():
height += document_layout.blockBoundingRect(block).height()
if not expanded and height > self._collapsed_height:
break
block = block.next()
self.expand_button.setVisible(expanded or height > self._collapsed_height)
target = max(self._collapsed_height, ceil(height)) if expanded else self._collapsed_height
if self._hint_height != target:
self._hint_height = target
self.setFixedHeight(target)
self.updateGeometry()
def resizeEvent(self, event: QResizeEvent) -> None: # noqa: N802 - Qt API
super().resizeEvent(event)
if hasattr(self, "_height_sync"):
self._height_sync.start()
def showEvent(self, event: QShowEvent) -> None: # noqa: N802 - Qt API
super().showEvent(event)
self._height_sync.start()
def changeEvent(self, event: QEvent) -> None: # noqa: N802 - Qt API
super().changeEvent(event)
if event.type() in (QEvent.Type.FontChange, QEvent.Type.StyleChange) and hasattr(
self, "_height_sync"
):
self._height_sync.start()
class DiagnosisComboBox(QComboBox):
"""Choice editor with the legacy text API used by diagnosis saving."""
def __init__(
@@ -46,6 +46,7 @@ from ..diagnosis_drawer import (
DiagnosisSwitch,
DiagnosisTabWidget,
DiagnosisTextEdit,
ExpandableDiagnosisTextEdit,
LoadingOverlay,
MessageStrip,
NotesTimeline,
@@ -300,7 +301,7 @@ _FORM_SECTIONS: tuple[tuple[str, tuple[tuple[tuple[str, str, int, str], ...], ..
("渠道", "create_source", 12, "create_source"),
),
(("统计端就诊卡", "show_card", 12, "show_card"),),
(("在用药物", "current_medications", 24, "textarea3"),),
(("在用药物", "current_medications", 24, "expandable_textarea3"),),
),
),
(
@@ -331,7 +332,7 @@ _FORM_SECTIONS: tuple[tuple[str, tuple[tuple[tuple[str, str, int, str], ...], ..
(("小便情况", "urine_condition", 24, "urine_condition_choices"),),
(("大便情况", "stool_condition", 24, "stool_condition_choices"),),
(("腰肾情况", "kidney_condition", 24, "kidney_condition_choices"),),
(("其他补充", "symptoms", 24, "textarea3"),),
(("其他补充", "symptoms", 24, "expandable_textarea3"),),
),
),
("既往史", ((("既往史", "past_history", 24, "past_history_choices"),),)),
@@ -1234,11 +1235,12 @@ class DiagnosisDialog(QDialog):
occupied = 0
for label, key, span, field_kind in row_fields:
editor = self._ensure_editor(key, field_kind)
display = (
editor.wrap_with_unit()
if isinstance(editor, DiagnosisNumberEdit)
else editor
)
if isinstance(editor, DiagnosisNumberEdit):
display = editor.wrap_with_unit()
elif isinstance(editor, ExpandableDiagnosisTextEdit):
display = editor.wrap_with_expander(label)
else:
display = editor
container = self._field_container(label, display, span, editor=editor)
row_layout.addWidget(container, span)
fields.append(container)
@@ -1371,9 +1373,13 @@ class DiagnosisDialog(QDialog):
columns=4 if multiple else 5,
)
self._choice_fields[key] = editor
elif field_kind.startswith("textarea"):
elif field_kind.startswith(("textarea", "expandable_textarea")):
rows = int(field_kind[-1])
editor = DiagnosisTextEdit(rows)
editor = (
ExpandableDiagnosisTextEdit(rows)
if field_kind.startswith("expandable_")
else DiagnosisTextEdit(rows)
)
else:
editor = DiagnosisLineEdit()
editor.setObjectName(f"DiagnosisField_{key.removeprefix('__')}")
@@ -259,6 +259,7 @@ QWidget#ReceptionPage QLabel#StatusBadge {
font-size: 11px;
font-weight: 500;
}
QWidget#ReceptionPage QPushButton#ReceptionNotifyButton,
QWidget#ReceptionPage QPushButton#ReceptionHistoryButton,
QWidget#ReceptionPage QPushButton#ReceptionImButton {
min-height: 34px;
@@ -282,6 +283,7 @@ QWidget#ReceptionPage QPushButton#ReceptionMoreButton {
font-size: 13px;
font-weight: 500;
}
QWidget#ReceptionPage QPushButton#ReceptionNotifyButton:hover,
QWidget#ReceptionPage QPushButton#ReceptionHistoryButton:hover,
QWidget#ReceptionPage QPushButton#ReceptionImButton:hover {
color: #5469F0;
@@ -3832,6 +3834,10 @@ class ReceptionPage(QWidget):
self.complete_button.clicked.connect(self._complete_appointment)
self.complete_button.setVisible(self._can_complete)
patient_head.addWidget(self.complete_button)
self.notify_button = QPushButton("通知医助", hero)
self.notify_button.setObjectName("ReceptionNotifyButton")
self.notify_button.clicked.connect(self._notify_assistant)
patient_head.addWidget(self.notify_button)
self.history_button = QPushButton("查看历史", hero)
self.history_button.setObjectName("ReceptionHistoryButton")
self.history_button.setIcon(_painted_reception_action_icon("info", "#3F4E75"))
@@ -3847,7 +3853,6 @@ class ReceptionPage(QWidget):
self.more_button = QPushButton("更多", hero)
self.more_button.setObjectName("ReceptionMoreButton")
more_menu = QMenu(self.more_button)
more_menu.addAction("通知医助").triggered.connect(self._notify_assistant)
edit_action = more_menu.addAction("编辑病历")
edit_action.setVisible(self._can_edit)
edit_action.triggered.connect(self._edit_diagnosis)
@@ -3865,8 +3870,6 @@ class ReceptionPage(QWidget):
action_compat = QWidget(hero)
action_compat.setFixedSize(0, 0)
action_compat.move(-100, -100)
self.notify_button = QPushButton("通知医助", action_compat)
self.notify_button.clicked.connect(self._notify_assistant)
self.edit_button = QPushButton("编辑病历", action_compat)
self.edit_button.setProperty("variant", "secondary")
self.edit_button.clicked.connect(self._edit_diagnosis)