from pathlib import Path p=Path(r'C:\kefu\wechat_rpa\wechat_gui_qt.py') s=p.read_text(encoding='utf-8-sig') start=s.index('class PatientBindingDialog('); end=s.index('class ConsoleBridge(',start) c=s[start:end] def replace(old,new): global c assert c.count(old)==1, (old[:80],c.count(old)) c=c.replace(old,new,1) replace(' operationFinished = Signal(object)\n',' operationFinished = Signal(object)\n reauthenticationFinished = Signal(object)\n') replace(' self.changed = False\n',' self.changed = False\n self._patient_session_expired = False\n self._busy = False\n') replace(' layout.addWidget(self.hint_label)\n',''' layout.addWidget(self.hint_label) self.reauthenticate_button = QPushButton("重新验证 ZYT 登录") self.reauthenticate_button.clicked.connect(self._reauthenticate) self.reauthenticate_button.hide() layout.addWidget(self.reauthenticate_button) ''') replace(' self.operationFinished.connect(self._operation_finished)\n',' self.operationFinished.connect(self._operation_finished)\n self.reauthenticationFinished.connect(self._reauthentication_finished)\n') replace(''' self.search_input.setEnabled(self.can_search and not busy) self.search_button.setEnabled(self.can_search and not busy) self.bind_button.setEnabled(self.can_bind and not busy) ''',''' self._busy = busy query_ready = not busy and not self._patient_session_expired self.search_input.setEnabled(self.can_search and not busy) self.search_button.setEnabled(self.can_search and query_ready) self.bind_button.setEnabled(self.can_bind and query_ready) self.reauthenticate_button.setEnabled(not busy) ''') replace(' def _search(self) -> None:\n',''' def _show_patient_session_expired(self) -> None: self._patient_session_expired = True self._patients = [] self.result_table.setRowCount(0) self.reauthenticate_button.show() self._set_busy(False, "ZYT 患者查询登录已过期,请点击下方按钮重新验证当前账号。" "验证后可重新搜索并核对患者,客服监听继续运行。") def _reauthenticate(self) -> None: if self._busy: return import backend_client account = str(backend_client.load_settings().get("zyt_account") or "当前账号") password, accepted = QInputDialog.getText( self, "重新验证 ZYT 登录", f"请输入 ZYT 账号 {account} 的密码:\n仅恢复患者查询登录,客服监听继续运行。", QLineEdit.Password, ) if not accepted: return if not password: self.hint_label.setText("请输入当前 ZYT 账号的密码后重试。") return self._set_busy(True, "正在验证 ZYT 账号并恢复患者查询…") person_id = str(self._selected_person().get("id") or "") def work() -> None: try: backend_client.reauthenticate_patient_session(password) context = backend_client.desktop_patient_context( self.external_account_id, self.conversation_external_id, person_id=person_id, ) payload = {"ok": True, "context": context} except Exception as exc: payload = {"ok": False, "error": str(exc)} try: self.reauthenticationFinished.emit(payload) except RuntimeError: pass threading.Thread(target=work, name="zyt-patient-reauthentication", daemon=True).start() @Slot(object) def _reauthentication_finished(self, payload: object) -> None: values = dict(payload or {}) if isinstance(payload, dict) else {} if not values.get("ok"): self._set_busy(False, f"验证失败:{values.get('error') or '请重试'}") return self.context = dict(values.get("context") or {}) self.permissions = dict(self.context.get("permissions") or {}) self.can_search = bool(self.permissions.get("can_search")) self.can_bind = bool(self.permissions.get("can_bind")) self.can_unbind = bool(self.permissions.get("can_unbind")) self.people = list(self.context.get("people") or []) self._patient_session_expired = False self.reauthenticate_button.hide() self._patients = [] self.result_table.setRowCount(0) self._person_changed() self._set_busy(False, "ZYT 登录已恢复,请搜索并核对患者后确认绑定。") if not self.can_search: self.hint_label.setText(str(self.permissions.get("reason") or "当前账号没有患者查询权限。")) elif self.search_input.text().strip(): self._search() def _search(self) -> None: if self._busy or self._patient_session_expired: return ''') # Escape the newline inside the password prompt's f-string in generated source. c=c.replace('的密码:\n仅恢复患者查询登录,客服监听继续运行。",','的密码:\\n仅恢复患者查询登录,客服监听继续运行。",') replace(' payload = {"ok": False, "error": str(exc)}\n try:\n self.searchFinished.emit(payload)',''' payload = {"ok": False, "error": str(exc), "patient_session_expired": isinstance(exc, backend_client.PatientSessionExpired)} try: self.searchFinished.emit(payload)''') replace(''' if not values.get("ok"): self.hint_label.setText(f"查询失败:{values.get('error') or '未知错误'}") ''',''' if not values.get("ok"): if values.get("patient_session_expired"): self._show_patient_session_expired() return self.hint_label.setText(f"查询失败:{values.get('error') or '未知错误'}") ''') replace(' def _bind_selected(self) -> None:\n',' def _bind_selected(self) -> None:\n if self._busy or self._patient_session_expired:\n return\n') replace(' payload = {"ok": False, "action": action, "error": str(exc)}',''' payload = {"ok": False, "action": action, "error": str(exc), "patient_session_expired": isinstance(exc, backend_client.PatientSessionExpired)}''') replace(''' if not values.get("ok"): self.hint_label.setText( f"操作失败:{values.get('error') or '未知错误'}" ''',''' if not values.get("ok"): if values.get("patient_session_expired"): self._show_patient_session_expired() return self.hint_label.setText( f"操作失败:{values.get('error') or '未知错误'}" ''') s=s[:start]+c+s[end:] s=s.replace('class MainWindow(QMainWindow):\n desktopSessionChecked = Signal(str, object)\n','class MainWindow(QMainWindow):\n desktopSessionChecked = Signal(str, object)\n patientSessionChecked = Signal(object)\n',1) marker=' def _check_desktop_session(self) -> None:\n' assert s.count(marker)==1 s=s.replace(marker,''' def _keepalive_patient_session(self) -> None: if self._patient_keepalive_running or self._forced_logout_handled: return self._patient_keepalive_running = True def check() -> None: try: import backend_client result = backend_client.keepalive_zyt_session() except Exception: result = {"state": "unreachable"} try: self.patientSessionChecked.emit(result) except RuntimeError: pass threading.Thread(target=check, name="zyt-session-keepalive", daemon=True).start() @Slot(object) def _patient_session_checked(self, result: object) -> None: self._patient_keepalive_running = False values = dict(result or {}) if isinstance(result, dict) else {} state = str(values.get("state") or "unreachable") if state == "skipped": return previous = self._patient_session_state self._patient_session_state = state if state == "expired" and previous != state: self.append_log("ZYT 患者查询登录已过期,可在绑定患者窗口重新验证;客服监听继续运行。", "warn") elif state == "active" and previous == "expired": self.append_log("ZYT 患者查询登录已恢复。", "ok") '''+marker,1) marker=' self._desktop_session_check_running = False\n self._desktop_session_state = "valid"\n' assert s.count(marker)==1 s=s.replace(marker,''' self._patient_keepalive_running = False self._patient_session_state = "unknown" self.patientSessionChecked.connect(self._patient_session_checked) self.patient_session_timer = QTimer(self) self.patient_session_timer.setInterval(30 * 60 * 1000) self.patient_session_timer.timeout.connect(self._keepalive_patient_session) if "--qt-smoke-test" not in sys.argv: self.patient_session_timer.start() QTimer.singleShot(5000, self._keepalive_patient_session) '''+marker,1) compile(s,str(p),'exec') p.write_text(s,encoding='utf-8',newline='\n') print('source GUI updated')