This commit is contained in:
Your Name
2026-08-12 11:03:28 +08:00
parent 09d3fcbf82
commit bc9ad1d3cd
32 changed files with 4586 additions and 2341 deletions
+151 -44
View File
@@ -9,7 +9,7 @@ import time
from contextlib import suppress
from typing import Any
from PySide6.QtCore import QObject, Qt, QTimer
from PySide6.QtCore import QLibraryInfo, QLocale, QObject, Qt, QTimer, QTranslator
from PySide6.QtGui import QGuiApplication, QIcon
from PySide6.QtWidgets import (
QApplication,
@@ -43,7 +43,71 @@ from doctor_workstation.ui.widgets import (
from doctor_workstation.video import BackendMode, launch_video_call
from doctor_workstation.video.window import WEBENGINE_AVAILABLE
LOGGER = logging.getLogger(__name__)
LOGGER = logging.getLogger(__name__)
class _ChineseQtTranslator(QTranslator):
"""Guarantee Chinese labels for common Qt standard buttons.
Qt's packaged ``qtbase_zh_CN`` catalog remains the primary source for
framework text. This small fallback also keeps release builds localized
when a packager omits the optional ``.qm`` files.
"""
_BUTTON_TEXT = {
"OK": "确定",
"Open": "打开",
"Save": "保存",
"Save All": "全部保存",
"Cancel": "取消",
"Close": "关闭",
"Yes": "",
"Yes to All": "全部确认",
"No": "",
"No to All": "全部否定",
"Abort": "中止",
"Retry": "重试",
"Ignore": "忽略",
"Discard": "放弃",
"Help": "帮助",
"Apply": "应用",
"Reset": "重置",
"Restore Defaults": "恢复默认设置",
"Don't Save": "不保存",
}
def translate(
self,
context: str,
source_text: str,
disambiguation: str | None = None,
n: int = -1,
) -> str:
del context, disambiguation, n
return self._BUTTON_TEXT.get(source_text.replace("&", ""), "")
def _install_chinese_translations(application: QApplication) -> None:
"""Install Simplified Chinese Qt catalogs once for the whole process."""
if getattr(application, "_doctor_workstation_chinese_translators", None):
return
QLocale.setDefault(QLocale("zh_CN"))
translators: list[QTranslator] = []
translations_path = QLibraryInfo.path(
QLibraryInfo.LibraryPath.TranslationsPath
)
for catalog in ("qt_zh_CN", "qtbase_zh_CN"):
translator = QTranslator(application)
if translator.load(catalog, translations_path):
application.installTranslator(translator)
translators.append(translator)
fallback = _ChineseQtTranslator(application)
application.installTranslator(fallback)
translators.append(fallback)
application._doctor_workstation_chinese_translators = translators # type: ignore[attr-defined]
class _UnconfiguredRepository:
@@ -200,11 +264,12 @@ class ApplicationController(QObject):
def _show_login(self) -> None:
if self.login_window is None:
self.login_window = LoginWindow(
self._base_repository(),
self.config,
self.demo_repository,
)
self.login_window = LoginWindow(
self._base_repository(),
self.config,
self.demo_repository,
credential_store=self.token_store,
)
self.login_window.login_succeeded.connect(self._on_login_succeeded)
self.login_window.config_changed.connect(self._on_config_changed)
self.login_window.demo_mode_changed.connect(self._on_demo_mode_changed)
@@ -212,8 +277,9 @@ class ApplicationController(QObject):
else:
self.login_window.repository = self._base_repository()
self.login_window.config = self.config
if not self.login_window.demo_check.isChecked():
self.login_window.active_repository = self._base_repository()
if not self.login_window.demo_check.isChecked():
self.login_window.active_repository = self._base_repository()
self.login_window.restore_remembered_credentials()
self.login_window.show()
self.login_window.raise_()
self.login_window.activateWindow()
@@ -498,20 +564,40 @@ class ApplicationController(QObject):
if parent is None or self.current_repository is None:
return
patient_id = payload.get("patient_id")
diagnosis_id = payload.get("diagnosis_id")
patient_name = str(payload.get("patient_name") or "患者")
diagnosis_id = payload.get("diagnosis_id")
patient_name = str(payload.get("patient_name") or "患者")
open_im = str(payload.get("mode") or "video").lower() == "im"
if patient_id in (None, "") or diagnosis_id in (None, ""):
show_toast(parent, "患者或诊单信息不完整,无法发起视频。", "danger", 4200)
return
call_key = str(diagnosis_id)
if (
call_key in self.video_pending
or call_key in self.video_calls
or call_key in self.demo_video_dialogs
):
show_toast(parent, "该问诊的视频正在准备或通话中。", "info", 3600)
return
call_key = str(diagnosis_id)
existing_call = self.video_calls.get(call_key)
if open_im and existing_call is not None and getattr(existing_call, "open_im", False):
qt_window = getattr(existing_call, "qt_window", None)
if qt_window is not None:
qt_window.show()
qt_window.raise_()
qt_window.activateWindow()
show_toast(parent, "该患者的 IM 会话已经打开。", "info", 3200)
return
if (
call_key in self.video_pending
or existing_call is not None
or call_key in self.demo_video_dialogs
):
show_toast(parent, "该问诊的视频正在准备或通话中。", "info", 3600)
return
closed_previous_im = False
if open_im:
for key, call in tuple(self.video_calls.items()):
if key == call_key or not getattr(call, "open_im", False):
continue
closed_previous_im = True
self.video_calls.pop(key, None)
with suppress(Exception):
call.close()
if self.current_demo_mode:
dialog = DemoVideoDialog(patient_name, parent)
@@ -525,7 +611,11 @@ class ApplicationController(QObject):
dialog.show()
return
show_toast(parent, "正在获取安全通话凭证…", "info")
show_toast(
parent,
"正在打开患者 IM 会话…" if open_im else "正在获取安全通话凭证…",
"info",
)
repository = self.current_repository
marker = object()
self.video_pending[call_key] = marker
@@ -536,23 +626,35 @@ class ApplicationController(QObject):
diagnosis_id=int(diagnosis_id),
)
run_async(
get_ticket,
on_success=lambda ticket: self._launch_video(
ticket,
diagnosis_id=diagnosis_id,
patient_id=patient_id,
repository=repository,
call_key=call_key,
marker=marker,
),
on_error=lambda error: self._video_ticket_error(
call_key,
marker,
parent,
error,
),
)
def request_ticket() -> None:
if self.video_pending.get(call_key) is not marker:
return
run_async(
get_ticket,
on_success=lambda ticket: self._launch_video(
ticket,
diagnosis_id=diagnosis_id,
patient_id=patient_id,
repository=repository,
call_key=call_key,
marker=marker,
open_im=open_im,
patient_name=patient_name,
),
on_error=lambda error: self._video_ticket_error(
call_key,
marker,
parent,
error,
),
)
# Tencent IM may take a brief moment to release the previous browser
# connection. The admin version also has only one ChatDialog instance.
if closed_previous_im:
QTimer.singleShot(400, request_ticket)
else:
request_ticket()
def _video_ticket_error(
self,
@@ -579,9 +681,11 @@ class ApplicationController(QObject):
diagnosis_id: Any,
patient_id: Any,
repository: Any,
call_key: str,
marker: object,
) -> None:
call_key: str,
marker: object,
open_im: bool = False,
patient_name: str = "患者",
) -> None:
if self.video_pending.get(call_key) is not marker:
return
self.video_pending.pop(call_key, None)
@@ -604,9 +708,11 @@ class ApplicationController(QObject):
patient_id=patient_id,
backend_mode=mode,
local_dist=video_dist_path(),
remote_url=self.config.video_web_url or None,
logger=logging.getLogger("doctor_workstation.video"),
)
remote_url=self.config.video_web_url or None,
logger=logging.getLogger("doctor_workstation.video"),
open_im=open_im,
patient_name=patient_name,
)
except Exception as error:
LOGGER.exception("video call could not be launched")
show_toast(
@@ -682,7 +788,8 @@ def _create_application(argv: list[str]) -> QApplication:
QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
application = QApplication(argv)
application = QApplication(argv)
_install_chinese_translations(application)
application.setApplicationName("甄养堂医生工作站")
application.setApplicationDisplayName("甄养堂医生工作站")
application.setOrganizationName("ZhenYangTang")