From 01ee694a3bb8048372fc793a8de314d70b688b35 Mon Sep 17 00:00:00 2001
From: yxh <172933527@qq.com>
Date: 星期日, 18 十月 2026 22:36:21 +0800
Subject: [PATCH] 修改了工位

---
 src/views/ecg/checktype/QueueSequenceForm.vue |  320 ++++++++++++++++++-------
 src/views/ecg/doctor/DevInstall.vue           |   28 +
 src/views/ecg/room/index.vue                  |   50 ++++
 src/api/ecg/room/index.ts                     |   10 
 src/views/ecg/doctor/Routine.vue              |   28 +
 src/hooks/web/useIdleLogout.ts                |   28 +-
 src/views/ecg/doctor/index.vue                |   28 +
 src/utils/seatGuard.ts                        |   73 ++++++
 src/config/axios/service.ts                   |   38 ++
 src/permission.ts                             |   13 
 src/utils/releaseBed.ts                       |   66 +----
 11 files changed, 486 insertions(+), 196 deletions(-)

diff --git a/src/api/ecg/room/index.ts b/src/api/ecg/room/index.ts
index af99804..563e304 100644
--- a/src/api/ecg/room/index.ts
+++ b/src/api/ecg/room/index.ts
@@ -167,6 +167,16 @@
     return await request.get({ url: `/clinic/room/close-biz` })
   },
 
+  /**
+   * 绠$悊鍛樺己鍒舵竻绌哄伐浣嶏紙鍖荤敓涓嬬彮蹇樼搴� / 鎹汉浣跨敤鍚屼竴宸ヤ綅锛夈��
+   *
+   * 涓庡尰鐢熻嚜宸辩殑銆岀搴с�嶄笉鍚岋細杩欓噷鍙互娓�**浠讳綍浜�**鍗犵敤鐨勫伐浣嶃��
+   * 鍚庣鍙湪璇ュ伐浣嶅浜庛�屽尰鐢熷湪宀� / 鏆傚仠銆嶄笖宸茶鍗犵敤鏃舵墠鐢熸晥锛岀┖闂插伐浣嶄細鎶�"鐘舵�佷笉鍏佽"銆�
+   */
+  forceClearBed: async (params: { roomId: number | null; bedNo: string | null }) => {
+    return await request.post({ url: `/clinic/room/force-clear-bed`, params })
+  },
+
   /** 銆岄�夋嫨宸ヤ綅妯″紡銆嶆寜鏈満IP鍖归厤宸ヤ綅锛堟ā寮� 1/3锛夛細杩斿洖鍚庣璇嗗埆鍒扮殑IP + 鍊欓�夊伐浣嶏紙宸叉帓搴忥級 */
   getIpBed: async () => {
     return await request.get({ url: `/clinic/room/get-ip-bed` })
diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts
index 30f9e26..873eec7 100644
--- a/src/config/axios/service.ts
+++ b/src/config/axios/service.ts
@@ -14,7 +14,6 @@
 
 import { resetRouter } from '@/router'
 import { deleteUserCache } from '@/hooks/web/useCache'
-import { releaseBedOnExit } from '@/utils/releaseBed'
 
 const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
 const { result_code, base_url, request_timeout } = config
@@ -24,6 +23,17 @@
   '鏃犳晥鐨勫埛鏂颁护鐗�', // 鍒锋柊浠ょ墝琚垹闄ゆ椂锛屼笉鐢ㄦ彁绀�
   '鍒锋柊浠ょ墝宸茶繃鏈�' // 浣跨敤鍒锋柊浠ょ墝锛屽埛鏂拌幏鍙栨柊鐨勮闂护鐗屾椂锛岀粨鏋滃洜涓鸿繃鏈熷け璐ワ紝姝ゆ椂闇�瑕佸拷鐣ャ�傚惁鍒欙紝浼氬鑷寸户缁� 401锛屾棤娉曡烦杞埌鐧诲嚭鐣岄潰
 ]
+
+/**
+ * 銆愭湰闇�姹傘��"宸ヤ綅澶辨晥"绫讳笟鍔$爜锛�**涓嶅湪杩欓噷寮瑰叏灞�鎻愮ず**銆�
+ *
+ * 宸ヤ綔鍙版瘡 5 绉掕疆璇竴娆★紝鑻ヨ繖閲屾瘡娆¢兘 `ElNotification.error`锛岀幇鍦哄氨浼氱湅鍒�
+ * 銆岃閲嶆柊灏卞骇銆嶄竴鐩村湪璺炽�傛敼鐢卞伐浣滃彴椤甸潰鐢� {@link utils/seatGuard} 涓�娆℃�ф彁绀猴紝
+ * 骞跺仠鎺夎嚜宸辩殑杞銆�
+ *
+ * 1_010_002_001 = 璇烽噸鏂板氨搴э紙ROOM_NOT_SIT锛�
+ */
+const SILENT_BIZ_CODES: number[] = [1010002001]
 // 鏄惁鏄剧ず閲嶆柊鐧诲綍
 export const isRelogin = { show: false }
 // Axios 鏃犳劅鐭ュ埛鏂颁护鐗岋紝鍙傝�� https://www.dashingdog.cn/article/11 涓� https://segmentfault.com/a/1190000020210980 瀹炵幇
@@ -177,9 +187,16 @@
         console.log(msg)
         return handleAuthorized()
       } else {
-        ElNotification.error({ title: msg })
+        // 銆愭湰闇�姹傘�戝伐浣嶅け鏁堢被涓氬姟鐮佷笉寮瑰叏灞�鎻愮ず锛堢敱宸ヤ綔鍙颁竴娆℃�ф彁绀� + 鍋滆疆璇級锛岄伩鍏嶆瘡 5 绉掑埛灞�
+        if (SILENT_BIZ_CODES.indexOf(Number(code)) === -1) {
+          ElNotification.error({ title: msg })
+        }
       }
-      return Promise.reject('error')
+      // 銆愭湰闇�姹傘�戞妸涓氬姟鐮佸甫杩涘紓甯稿璞★細椤甸潰鍙嵁姝よ瘑鍒�庡凡涓嶆槸璇ュ伐浣嶄富浜恒�忓苟鍋滄杞銆�
+      // 浠嶇敤 Error 瀹炰緥锛屼繚璇佹棦鏈夌殑 e?.message 鐢ㄦ硶涓嶅彈褰卞搷銆�
+      const bizErr: any = new Error(msg)
+      bizErr.code = Number(code)
+      return Promise.reject(bizErr)
     } else {
       return data
     }
@@ -207,13 +224,16 @@
 const handleAuthorized = () => {
   const { t } = useI18n()
 
-  // 銆愰噴鏀捐瘖鐤楀簥浣嶃�戦壌鏉冨け璐� 鈬� 鏈疆鐧诲綍鍗冲皢缁撴潫锛屽繀椤诲厛閲婃斁鍖荤敓鍗犵敤鐨勫簥浣嶏紝
-  // 鍚﹀垯璇ュ簥浣嶄細涓�鐩存樉绀鸿鍗犵敤锛坄clinic_room.doc_id` 涓嶄负绌猴級锛屽埆鐨勫尰鐢熼�変笉鍒般��
+  // 銆愭湰娆″彉鏇淬�戦壌鏉冨け璐�**涓嶅啀閲婃斁**璇婂宸ヤ綅锛坄clinic_room.doc_id`锛夈��
   //
-  // 鈿狅笍 椤哄簭瑕佹眰锛歳eleaseBedOnExit 鍐呴儴瑕佽 localStorage 閲岀殑璁块棶浠ょ墝鏉ュ甫
-  // Authorization 澶达紝鎵�浠�**蹇呴』鍦� removeToken() 涔嬪墠**璋冪敤锛堜笉 await锛�
-  // 浠ュ厤闃诲涓嬮潰鐨勬竻鐞嗕笌璺宠浆锛涘け璐ヤ篃涓嶅奖鍝嶆祦绋嬶級銆�
-  void releaseBedOnExit('auth-failed')
+  // 鍘熷疄鐜帮細401 / 鍒锋柊浠ょ墝澶辫触鏃朵細璋� `releaseBedOnExit('auth-failed')`锛�
+  // 鎶婂尰鐢熷崰鐢ㄧ殑宸ヤ綅娓呯┖銆備絾杩欎細鎶�"浠ょ墝鍒版湡"璇綋鎴�"鍖荤敓绂诲紑"锛�
+  // 鍖荤敓鍙槸绂诲紑鐢佃剳瓒呰繃 token 鏈夋晥鏈燂紙鎴栫綉缁滄姈鍔ㄥ鑷村埛鏂板け璐ワ級锛�
+  // 鍥炴潵灏辫杩噸鏂板叆搴э紝宸ヤ綔鍙拌繕浼氭彁绀�"璇烽噸鏂板氨鍧�"銆�
+  //
+  // 鐜板湪鐨勫彛寰勶細宸ヤ綅閲婃斁鍙彂鐢熷湪**鐢ㄦ埛涓诲姩鎿嶄綔**锛堟墜鍔ㄣ�岀搴с��/銆屾墜鍔ㄩ��鍑虹櫥褰曘�嶏級锛�
+  // 浠ょ墝澶辨晥鍙竻鏈湴鍑瘉骞惰烦鐧诲綍椤碉紝閲嶆柊鐧诲綍鍚庡伐浣嶄粛鍦紝鍙洿鎺ョ户缁娇鐢ㄣ��
+  // 鍖荤敓鐪熺殑涓嬬彮蹇樼搴х殑鍏滃簳锛氶棴璇婄粺涓�娓呯┖ + 璇婂绠$悊椤点�屾竻绌哄伐浣嶃�嶃��
 
   // 銆愪慨澶嶃�戦壌鏉冨け璐ユ椂**绔嬪嵆**娓呯悊鏈湴鍑瘉涓庣敤鎴风紦瀛橈紝涓嶅啀绛夌敤鎴风偣鍑诲脊绐椼��
   //
diff --git a/src/hooks/web/useIdleLogout.ts b/src/hooks/web/useIdleLogout.ts
index 22904c4..25be23a 100644
--- a/src/hooks/web/useIdleLogout.ts
+++ b/src/hooks/web/useIdleLogout.ts
@@ -1,7 +1,6 @@
 import { watch } from 'vue'
 import { useIdle } from '@vueuse/core'
 import { ElMessage } from 'element-plus'
-import { releaseBedOnExit } from '@/utils/releaseBed'
 
 /**
  * 绌洪棽瓒呮椂鑷姩閫�鍑猴紙闇�姹傦細30 鍒嗛挓涓嶆搷浣滄墠瑕佹眰閲嶆柊杈撳叆瀵嗙爜锛夈��
@@ -18,6 +17,7 @@
  *   路 鏈変换浣曢紶鏍�/閿洏/瑙︽懜/婊氬姩鎿嶄綔 鈫� 璁℃椂鍣ㄩ噸缃紱
  *   路 杩炵画 30 鍒嗛挓**鏃犱换浣曟搷浣�** 鈫� 涓诲姩娓呯悊鐧诲綍鎬佸苟璺宠浆鐧诲綍椤碉紙瑕佹眰閲嶆柊杈撳瘑鐮侊級锛�
  *   路 鏈夋搷浣滄椂涓嶄富鍔ㄧ櫥鍑猴紙token 鑷韩鐨勭画鏈熺敱 axios 鎷︽埅鍣ㄧ殑 refresh-token 璐熻矗锛夈��
+ *   路 銆愭湰娆°�戠┖闂茬櫥鍑�**涓嶅啀閲婃斁璇婂宸ヤ綅**锛堝伐浣嶄繚鐣欙紝閲嶆柊鐧诲綍鍗冲彲缁х画浣跨敤锛夈��
  *
  * 鐢ㄦ硶锛堝湪 permission.ts 涓皟鐢ㄤ竴娆″嵆鍙紝鍐呴儴鑷甫骞傜瓑淇濇姢锛夛細
  *   setupIdleLogout()
@@ -99,26 +99,28 @@
 }
 
 /**
- * 瀹為檯鎵ц绌洪棽鐧诲嚭锛氶噴鏀惧簥浣� + 娓呯悊鏈湴鍑瘉骞惰烦杞櫥褰曢〉銆�
+ * 瀹為檯鎵ц绌洪棽鐧诲嚭锛�**鍙竻鏈湴鍑瘉骞惰烦杞櫥褰曢〉锛屼笉閲婃斁宸ヤ綅**銆�
  *
  * 涔嬫墍浠ヤ笉璋冪敤 userStore.loginOut()锛堥偅鏄�"涓诲姩閫�鍑�"锛夛細
  *   路 閭d釜鎺ュ彛浼氳姹傚悗绔敞閿� token 鈥� 瀵规湰鍦烘櫙娌″繀瑕侊紝涓斿彲鑳藉洜缃戠粶闂鍗′綇锛�
  *   路 绌洪棽鐧诲嚭鍙渶淇濊瘉鏈湴鍑瘉娓呭共鍑� + 鍥炲埌鐧诲綍椤点��
  *
- * 鈿狅笍 浣�**蹇呴』**鍗曠嫭璋冧竴娆°�岄噴鏀惧簥浣嶃�嶏細
- *   鍖荤敓鍦ㄣ�岃瘖瀹ら�夋嫨銆嶉〉閫夊ソ搴婁綅鍚庡氨鍗犵敤浜嗚搴婁綅锛坄clinic_room.doc_id`锛夈��
- *   鍘熷厛鏈嚱鏁板彧娓呮湰鍦板嚟璇併�佷笉閫氱煡鍚庣 鈬� 绌洪棽瓒呮椂鍚庡簥浣�**涓�鐩存樉绀鸿鍗犵敤**锛�
- *   鍏朵粬鍖荤敓閫変笉鍒拌繖涓簥浣嶃�傝繖姝f槸"鍖荤敓蹇樹簡绂诲骇灏遍��鍑虹郴缁�"鐨勪富瑕佸満鏅��
+ * 銆愰噸瑕佸彉鏇达紙鏈锛夈�戠┖闂茶秴鏃�**涓嶅啀閲婃斁璇婂宸ヤ綅**銆�
+ * ---------------------------------------------------------------
+ * 鍘熷厛鏈嚱鏁颁細鍏堣皟 `releaseBedOnExit('idle-timeout')`锛屾妸 `clinic_room.doc_id` 娓呯┖銆�
+ * 瀹炴祴闂锛氬尰鐢熺寮�鐢佃剳 30 鍒嗛挓浠ヤ笂锛堜緥濡傚幓鍋氭鏌ャ�佺寮� 1 灏忔椂鍥炴潵锛夛紝
+ * 鍥炴潵鏃跺伐浣嶅凡缁忚閲婃斁 鈫� 宸ヤ綔鍙板彧鑳藉彧璇汇�佸苟鎻愮ず"璇烽噸鏂板氨鍧�"锛�
+ * 鑰屽尰鐢熸湰浜哄苟娌℃湁绂诲骇銆傝繖涓嶇鍚堝疄闄呬娇鐢ㄥ満鏅��
+ *
+ * 鐜板湪鐨勫彛寰勶細
+ *   路 绌洪棽瓒呮椂 鈫� **鍙姹傞噸鏂拌緭鍏ュ瘑鐮�**锛坱oken 娓呮帀銆佸洖鐧诲綍椤碉級锛�**宸ヤ綅淇濈暀**锛�
+ *   路 閲嶆柊鐧诲綍鍚� `roomStore.fetchMySeat()` 浼氳嚜鍔ㄦ仮澶嶃�屾垜鐨勫伐浣嶃�嶏紝鍙洿鎺ョ户缁娇鐢紱
+ *   路 宸ヤ綅鐨勯噴鏀惧彧鍙戠敓鍦�**鐢ㄦ埛涓诲姩鎿嶄綔**锛氭墜鍔ㄣ�岀搴с�嶃�佹墜鍔ㄣ�岄��鍑虹櫥褰曘�嶃��
+ *   路 鍖荤敓鐪熺殑涓嬬彮蹇樼搴х殑鍏滃簳锛氣憼 闂瘖鏃剁郴缁熺粺涓�娓呯┖褰撶彮宸ヤ綅锛涒憽 绠$悊鍛樺彲鍦�
+ *     璇婂绠$悊椤点�屾竻绌哄伐浣嶃�嶃��
  */
 async function doIdleLogout(): Promise<void> {
   ElMessage.warning(`宸茶秴杩� ${IDLE_TIMEOUT_MINUTES} 鍒嗛挓鏈搷浣滐紝璇烽噸鏂扮櫥褰昤)
-
-  // 鍏堥噴鏀惧簥浣嶏紝鍐嶆竻鍑瘉 鈥斺�� 椤哄簭涓嶈兘鍙嶏細閲婃斁鎺ュ彛闇�瑕佹惡甯﹀綋鍓嶄护鐗�
-  try {
-    await releaseBedOnExit('idle-timeout')
-  } catch {
-    /* releaseBedOnExit 鍐呴儴宸插厹搴曪紝涓嶄細鎶涢敊锛涙澶勪粎涓洪槻寰℃�у啓娉� */
-  }
 
   cleanupCredentials()
 
diff --git a/src/permission.ts b/src/permission.ts
index fcdd1b2..719e23d 100644
--- a/src/permission.ts
+++ b/src/permission.ts
@@ -13,7 +13,6 @@
 import { useRoomStoreWithOut } from "@/store/modules/room";
 // 闇�姹傦細30 鍒嗛挓鏃犳搷浣滄墠瑕佹眰閲嶆柊鐧诲綍锛堝墠绔┖闂叉娴嬶級
 import { setupIdleLogout } from '@/hooks/web/useIdleLogout'
-import { setupBedReleaseOnUnload } from '@/utils/releaseBed'
 
 const { start, done } = useNProgress()
 
@@ -222,10 +221,12 @@
   // ==========================================================================
   if (getAccessToken() && to.path !== '/login') {
     setupIdleLogout()
-    // 闇�姹傦細閫�鍑虹郴缁熸椂鑷姩閫�鍑哄崰鐢ㄧ殑璇婄枟搴婁綅銆�
-    // 娉ㄥ唽 `pagehide` 鍏滃簳锛堝叧闂爣绛鹃〉/鍒锋柊/璺宠蛋锛夆�斺�� 鍐呴儴鑷甫骞傜瓑淇濇姢锛�
-    // 鍙槸"灏藉姏鑰屼负"鐨勬渶鍚庝竴灞傦紱涓昏淇濋殰鍦� useIdleLogout / userStore.loginOut /
-    // axios 鐨� handleAuthorized 涓夊鏄庣‘鐨勯��鍑鸿矾寰勪笂銆�
-    setupBedReleaseOnUnload()
+    // 銆愭湰娆″彉鏇淬��**宸茬Щ闄�** `setupBedReleaseOnUnload()`锛坧agehide + sendBeacon锛夈��
+    //
+    // 鍘熻涓猴細鍏抽棴鏍囩椤� / 鍒锋柊 / 璺宠蛋鏃堕兘浼氶噴鏀惧崰鐢ㄧ殑宸ヤ綅锛屽鑷�
+    //   路 鎸� F5 鍒锋柊涓�娆″氨涓㈠伐浣嶏紱
+    //   路 娴忚鍣ㄥ穿婧冩仮澶嶃�佸垏璧颁竴浼氬効鍥炴潵锛屼篃鍙兘鍥� pagehide 涓㈠伐浣嶃��
+    // 鐜板湪宸ヤ綅鍙湪**鐢ㄦ埛涓诲姩鎿嶄綔**鏃堕噴鏀撅細鎵嬪姩銆岀搴с�嶃�佹墜鍔ㄣ�岄��鍑虹櫥褰曘�嶃��
+    // 鍖荤敓涓嬬彮蹇樼搴х殑鍏滃簳锛氣憼 闂瘖鏃剁粺涓�娓呯┖褰撶彮宸ヤ綅锛涒憽 绠$悊鍛樸�屾竻绌哄伐浣嶃�嶃��
   }
 })
diff --git a/src/utils/releaseBed.ts b/src/utils/releaseBed.ts
index b3b3524..a642a49 100644
--- a/src/utils/releaseBed.ts
+++ b/src/utils/releaseBed.ts
@@ -2,7 +2,7 @@
 import { getAccessToken } from '@/utils/auth'
 
 /**
- * 閫�鍑虹郴缁熸椂鑷姩閲婃斁璇婂宸ヤ綅锛堥渶姹傦細鍖荤敓甯稿繕璁般�岀搴с�嶅氨閫�鍑虹郴缁燂級銆�
+ * 鎵嬪姩銆岄��鍑虹櫥褰曘�嶆椂閲婃斁璇婂宸ヤ綅銆�
  *
  * 鑳屾櫙
  * ----
@@ -10,20 +10,23 @@
  * 锛堝悗绔啓鍦� `clinic_room.doc_id` / `status`锛夈��
  * 鑻ュ尰鐢熶笉鐐广�岀搴с�嶈�岀洿鎺ラ��鍑虹郴缁燂紝搴婁綅浼氫竴鐩存樉绀鸿鍗犵敤锛屽叾浠栧尰鐢熸棤娉曚娇鐢ㄣ��
  *
- * 瑕嗙洊鐨勯��鍑鸿矾寰�
- * --------------
- *  1. 鎵嬪姩鐐广�岄��鍑虹櫥褰曘�� 鈫� 鍚庣 `/system/auth/logout` 宸蹭細绂诲骇锛圓uthController锛夛紝
- *     鏈嚱鏁板湪鍏朵箣鍓嶅啀鍏滀竴娆★紝淇濊瘉鍗充娇 logout 璇锋眰澶辫触涔熷凡閲婃斁锛�
- *  2. **绌洪棽 30 鍒嗛挓鑷姩鐧诲嚭**锛坄useIdleLogout`锛夆啋 鍘熷厛鍙竻鏈湴鍑瘉銆佷笉璋冨悗绔� 鈬� 搴婁綅涓嶉噴鏀撅紝
- *     杩欐槸鏈淇鐨勬牳蹇冨満鏅紱
- *  3. **浠ょ墝澶辨晥/鍒锋柊澶辫触琚己鍒剁櫥鍑�**锛坄config/axios/service.ts` 鐨� `handleAuthorized`锛夛紱
- *  4. 鍏抽棴/鍒锋柊椤甸潰鍓嶇殑 `pagehide` 鍏滃簳銆�
+ * 銆愰噸瑕佸彉鏇达紙鏈锛夈�戝彧淇濈暀銆屾墜鍔ㄩ��鍑虹櫥褰曘�嶈繖涓�鏉¤矾寰�
+ * ---------------------------------------------------------------
+ * 鍘熷厛瑕嗙洊 4 鏉¤矾寰勶細鎵嬪姩閫�鍑恒�佺┖闂� 30 鍒嗛挓鑷姩鐧诲嚭銆佷护鐗屽け鏁�(401)銆乣pagehide`锛堝埛鏂�/鍏抽〉锛夈��
+ * 瀹炴祴闂锛氬悗涓夋潯浼氭妸銆屼护鐗屽埌鏈� / 鍒锋柊椤甸潰 / 绂诲紑涓�浼氬効銆嶈褰撴垚銆屽尰鐢熺搴с�嶏紝
+ * 瀵艰嚧宸ヤ綅琚竻绌� 鈥斺�� 鍖荤敓鍥炴潵鐪嬪埌宸ヤ綔鍙板彧鍓╁彧璇汇�佸苟鎻愮ず"璇烽噸鏂板氨鍧�"锛岃�屽伐浣嶅凡鍙樼┖闂层��
+ *
+ * 鐜板湪鐨勫彛寰勶細
+ *   路 **鍙湁鎵嬪姩銆岄��鍑虹櫥褰曘��**锛堜互鍙婅瘖瀹ら�夋嫨椤电殑鎵嬪姩銆岀搴с�嶏級鎵嶉噴鏀惧伐浣嶏紱
+ *   路 绌洪棽瓒呮椂锛坄useIdleLogout`锛夊彧瑕佹眰閲嶆柊杈撳瘑鐮侊紝宸ヤ綅淇濈暀锛�
+ *   路 401 / 鍒锋柊浠ょ墝澶辫触鍙竻鏈湴鍑瘉锛屽伐浣嶄繚鐣欙紱
+ *   路 涓嶅啀娉ㄥ唽 `pagehide` 鍏滃簳锛堝埛鏂伴〉闈笉搴旇涓㈠伐浣嶏級锛�
+ *   路 鍖荤敓鐪熺殑涓嬬彮蹇樼搴х殑鍏滃簳锛氣憼 闂瘖鏃剁郴缁熺粺涓�娓呯┖褰撶彮宸ヤ綅锛涒憽 绠$悊鍛樸�屾竻绌哄伐浣嶃�嶃��
  *
  * 涓轰粈涔堢敤**瑁� axios** 鑰屼笉鏄」鐩殑 request
  * ----------------------------------------
- * 椤圭洰鐨� `request` 鎸備簡 401 鎷︽埅鍣紝鑰屾湰鍑芥暟**鎭版伆鏄湪 401 涔嬪悗**琚皟鐢ㄧ殑銆�
- * 鑻ヨ蛋鎷︽埅鍣紝浼氬啀娆¤Е鍙�"鍒锋柊浠ょ墝澶辫触 鈫� 寮哄埗鐧诲嚭 鈫� 鍐嶈皟鏈嚱鏁�"鐨勯�掑綊銆�
- * 瑁� axios 涓嶆寕浠讳綍鎷︽埅鍣紝澶╃劧鏂紑杩欐潯閾俱��
+ * 鏈嚱鏁板彲鑳藉湪閴存潈鐘舵�佷笉绋虫椂琚皟鐢紝鑻ヨ蛋椤圭洰 request 鐨� 401 鎷︽埅鍣紝
+ * 浼氬啀娆¤Е鍙�"鍒锋柊浠ょ墝澶辫触 鈫� 寮哄埗鐧诲嚭"鐨勯�掑綊銆傝8 axios 涓嶆寕浠讳綍鎷︽埅鍣紝澶╃劧鏂紑杩欐潯閾俱��
  *
  * 涓轰粈涔堢敤**鏈嶅姟绔寜 userId 鍙嶆煡**鑰屼笉鏄墠绔紶搴婁綅鍙�
  * ------------------------------------------------
@@ -31,9 +34,7 @@
  * 灏变細"閲婃斁涓嶆帀"銆傛晠鍚庣鎻愪緵鏃犲弬鎺ュ彛 `/ecg/doctor/release-my-beds`锛屾寜褰撳墠鐧诲綍鐢ㄦ埛
  * 鍙嶆煡鍏跺崰鐢ㄧ殑鍏ㄩ儴搴婁綅銆傚墠绔彧闇�甯︿笂浠ょ墝銆�
  *
- * 鈿狅笍 鏈枃浠�**涓嶈 import `@/config/axios/config`**锛�
- *   `config/axios/service.ts` 闇�瑕� import 鏈枃浠讹紙鍦� 401 澶勭悊閲岄噴鏀惧簥浣嶏級锛�
- *   鑻ユ湰鏂囦欢鍙嶅悜 import 瀹冪殑 config锛屼細褰㈡垚寰幆渚濊禆銆�
+ * 鈿狅笍 鏈枃浠�**涓嶈 import `@/config/axios/config`**锛氶伩鍏嶄笌 service.ts 褰㈡垚寰幆渚濊禆銆�
  *   杩欓噷鐩存帴鐢ㄥ悓涓�涓幆澧冨彉閲忔嫾鎺ワ紝鏁堟灉绛変环涓旀棤鐜��
  *
  * 澶辫触澶勭悊锛氭湰鍑芥暟**姘镐笉鎶涘紓甯�**锛堥��鍑烘祦绋嬩笉搴斿洜瀹冭�屼腑鏂級銆�
@@ -68,39 +69,4 @@
   } finally {
     releasing = false
   }
-}
-
-/**
- * 娉ㄥ唽 `pagehide` 鍏滃簳锛氬叧闂爣绛鹃〉/鍒锋柊/璺宠蛋鏃跺皾璇曢噴鏀惧簥浣嶃��
- *
- * 鐢� `sendBeacon` 鑰岄潪 ajax 鈥斺�� 椤甸潰鍗歌浇鏃舵櫘閫氳姹備細琚祻瑙堝櫒鍙栨秷锛宐eacon 涓嶄細銆�
- * 浣� beacon **鏃犳硶鑷畾涔夎姹傚ご**锛堜笉鑳藉甫 Authorization锛夛紝鏁呮敼鐢� URL 浼犱护鐗岋細
- * 鍚庣 `SecurityProperties.tokenParameter` 榛樿灏辨槸 `"token"`锛�
- * 涓� `TokenAuthenticationFilter` 浼氳鍙栧畠锛堣 `obtainAuthorization`锛夈��
- *
- * 娉ㄦ剰锛氭湰椤规槸**灏藉姏鑰屼负**鐨勫厹搴曪紝涓嶄綔涓轰富瑕佹墜娈� 鈥斺��
- * 涓昏鎵嬫鏄笂闈� 1/2/3 涓夋潯鏄庣‘璺緞銆�
- */
-export function setupBedReleaseOnUnload(): void {
-  if (typeof window === 'undefined') return
-  // 骞傜瓑锛氶伩鍏� HMR / 閲嶅璋冪敤瀵艰嚧閲嶅娉ㄥ唽
-  if ((window as any).__bedReleaseUnloadBound) return
-  ;(window as any).__bedReleaseUnloadBound = true
-
-  window.addEventListener('pagehide', () => {
-    const token = getAccessToken()
-    if (!token) return
-    try {
-      const url = `${BASE_URL}/ecg/doctor/release-my-beds?token=${encodeURIComponent(token)}`
-      const ok = navigator.sendBeacon?.(url)
-      if (!ok) {
-        // sendBeacon 涓嶅彲鐢�/琚嫆缁濇椂锛岄��鍥炲悓姝� XHR 鏈�鍚庝竴鎼�
-        const xhr = new XMLHttpRequest()
-        xhr.open('POST', url, false) // 鍚屾锛岀‘淇濆嵏杞藉墠鍙戝嚭
-        xhr.send()
-      }
-    } catch {
-      /* 蹇界暐锛氬嵏杞介樁娈典笉搴旀姤閿� */
-    }
-  })
 }
diff --git a/src/utils/seatGuard.ts b/src/utils/seatGuard.ts
new file mode 100644
index 0000000..492501a
--- /dev/null
+++ b/src/utils/seatGuard.ts
@@ -0,0 +1,73 @@
+import { ElMessageBox } from 'element-plus'
+
+/**
+ * 宸ヤ綅瀹堝崼锛氥�岃閲嶆柊灏卞骇銆嶇被閿欒鐨勪竴娆℃�ф彁绀� + 杞鍋滄杈呭姪銆�
+ *
+ * 鑳屾櫙
+ * ----
+ * 宸ヤ綔鍙帮紙鍙彿妫�鏌� / 璁惧棰嗙敤 / 璁惧瑁呮満锛夋瘡 **5 绉�** 杞涓�娆� `bed-doctor-get`銆�
+ * 褰撳綋鍓嶈处鍙峰凡缁忎笉鏄宸ヤ綅鐨勪富浜猴紙宸ヤ綅琚噴鏀� / 鍦ㄥ埆鐨勭數鑴戝叆搴� / 琚鐞嗗憳娓呯┖锛夋椂锛�
+ * 鍚庣杩斿洖涓氬姟鐮� `1_010_002_001`銆岃閲嶆柊灏卞骇銆嶏紝鍓嶇鍘熷厛**姣忚疆閮藉脊涓�娆�**锛�
+ * 鐜板満鐪嬪埌鐨勫氨鏄�"绯荤粺涓�鐩磋烦鍑鸿閲嶆柊灏卞骇!"銆�
+ *
+ * 杩欓噷鎻愪緵涓や釜鑳藉姏锛�
+ *   路 鍙彁绀轰竴娆★紙涓�娆℃�у脊绐楋紝甯︺�屽幓閫夊伐浣嶃�嶏級锛屼笉鍐嶅埛灞忥紱
+ *   路 椤甸潰鎹**鍋滄帀杞瀹氭椂鍣�**锛岀瓑閲嶆柊閫夊埌宸ヤ綅鍚庡啀鎭㈠銆�
+ *
+ * 鍒ゅ畾渚濇嵁锛氬悗绔� {@code ErrorCodeConstants.ROOM_NOT_SIT = 1_010_002_001}銆�
+ */
+
+/** 銆岃閲嶆柊灏卞骇銆嶄笟鍔$爜锛堝悗绔� ROOM_NOT_SIT锛� */
+export const SEAT_INVALID_CODE = 1010002001
+
+/** 鏌愪釜璇锋眰寮傚父鏄惁灞炰簬"宸蹭笉鏄宸ヤ綅涓讳汉" */
+export function isSeatInvalidError(e: any): boolean {
+  if (!e) return false
+  const code = Number(e.code ?? e.bizCode ?? NaN)
+  if (Number.isFinite(code) && code === SEAT_INVALID_CODE) return true
+  // 鍏滃簳锛氫釜鍒摼璺彧鎶� msg 浼犱笂鏉�
+  const msg = String(e.message ?? e.msg ?? e)
+  return msg.indexOf('璇烽噸鏂板氨搴�') !== -1
+}
+
+let notified = false
+
+/** 鏄惁宸茬粡鎻愮ず杩囷紙渚夸簬椤甸潰鍒ゆ柇锛� */
+export function isSeatInvalidNotified(): boolean {
+  return notified
+}
+
+/**
+ * 宸ヤ綅澶辨晥鎻愮ず锛�**鏁翠釜浼氳瘽鍙脊涓�娆�**锛岄伩鍏� 5 绉掕疆璇㈡妸鎻愮ず鍒锋垚"涓�鐩磋烦"銆�
+ *
+ * 鐐广�屽幓閫夊伐浣嶃�嶁啋 鏁撮〉璺宠浆鍒伴�夊伐浣嶉〉锛堢敤 location 鑰岄潪 router.push锛�
+ * 閫夊伐浣嶉〉鏄《灞傝矾鐢憋紝鏁撮〉璺宠浆鑳介『甯﹂噸缃悇宸ヤ綔鍙扮殑瀹氭椂鍣ㄤ笌鏈湴鐘舵�侊級銆�
+ */
+export function notifySeatInvalidOnce(): void {
+  if (notified) return
+  notified = true
+  ElMessageBox.confirm(
+    '褰撳墠璐﹀彿宸蹭笉鏄繖涓伐浣嶇殑涓讳汉锛堝彲鑳藉伐浣嶅凡琚噴鏀俱�佸湪鍏跺畠鐢佃剳涓婂叆搴э紝鎴栬绠$悊鍛樻竻绌猴級銆俓n' +
+      '璇烽噸鏂伴�夋嫨宸ヤ綅鍚庡啀鎿嶄綔銆�',
+    '璇烽噸鏂板氨搴�',
+    {
+      confirmButtonText: '鍘婚�夊伐浣�',
+      cancelButtonText: '绋嶅悗',
+      type: 'warning',
+      closeOnClickModal: false
+    }
+  )
+    .then(() => {
+      window.location.href = '/roomselect'
+    })
+    .catch(() => {
+      /* 鐢ㄦ埛閫夋嫨銆岀◢鍚庛�嶏細淇濇寔椤甸潰鍙锛屼笉鍐嶉噸澶嶅脊 */
+    })
+}
+
+/**
+ * 閲嶆柊鑾峰緱宸ヤ綅锛堥噸鏂板叆搴� / 鍒囨崲宸ヤ綅 / 鑷姩鎭㈠锛夊悗澶嶄綅锛屽厑璁镐笅娆″啀鎻愮ず涓�娆°��
+ */
+export function resetSeatInvalidNotify(): void {
+  notified = false
+}
diff --git a/src/views/ecg/checktype/QueueSequenceForm.vue b/src/views/ecg/checktype/QueueSequenceForm.vue
index 6b43610..56844a0 100644
--- a/src/views/ecg/checktype/QueueSequenceForm.vue
+++ b/src/views/ecg/checktype/QueueSequenceForm.vue
@@ -7,7 +7,7 @@
 
     鑳屾櫙锛氳琛ㄥ師鍏堟瘡澶╄嚜鍔ㄧ敓鎴愶紝鐜板凡鏀逛负鎵嬪伐缁存姢锛屾鍓嶆病鏈夌淮鎶ょ晫闈€��
   -->
-  <Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="65%" >
     <!-- 椤堕儴璇存槑锛氳繖浜涘瓧娈电殑鍚箟涓�"姣忔棩閲嶇疆"鐨勫叧绯� -->
     <el-alert type="info" :closable="false" class="mb-10px">
       <template #title>
@@ -19,7 +19,8 @@
       <div class="text-12px leading-5">
         路 <b>璧峰搴忓彿</b>锛氭瘡澶╄嚜鍔ㄩ噸缃椂鍥炲埌璇ュ�硷紱<b>涓婇檺</b>锛氬埌杈惧悗璇ユ椂娈佃涓恒�屽凡婊°�嶏紝鎮h�呯鍒颁細椤哄欢鍒板悗缁椂娈点��<br />
         路 <b>褰撳墠搴忓彿</b>锛氬凡鍙戝埌鍑犲彿锛屼竴鑸棤闇�鎵嬪伐鏀癸紙姣忔棩閲嶇疆浼氳嚜鍔ㄥ洖鍒�"璧峰搴忓彿"锛夈��<br />
-        路 <b>搴忓彿涓婇檺蹇呴』澶т簬璧峰搴忓彿</b>锛屽惁鍒欒鏃舵姘歌繙鏃犳硶鍙栧彿銆�<br />
+        路 <b>棰勫畾棰濆害</b> = 涓婇檺 鈭� 璧峰搴忓彿锛堝彧璇伙級銆�<b>涓婇檺 = 璧峰搴忓彿 琛ㄧず璇ユ椂娈垫病鏈夊悕棰�</b>锛堝厑璁镐繚瀛橈紝棰勫畾棰濆害鏄剧ず 0 骞舵爣绾級銆�<br />
+        路 涓婇檺 <b>涓嶈兘灏忎簬</b>璧峰搴忓彿锛屽惁鍒欒鏃舵姘歌繙鏃犳硶鍙栧彿銆�<br />
         路 鍚屼竴妫�鏌ラ」鐩笅<b>鍚屼竴鏃舵鍙兘鏈変竴鏉¤鍒�</b>锛岄噸澶嶆坊鍔犱細琚嫆缁濄��
       </div>
     </el-alert>
@@ -36,107 +37,155 @@
       </span>
     </div>
 
-    <el-table v-loading="loading" :data="list" :stripe="true" border height="420px">
-      <el-table-column label="鏃舵" align="center" min-width="130px">
+    <el-table
+      v-loading="loading"
+      :data="list"
+      :stripe="true"
+      border
+      class="qs-table"
+      height="calc(100vh - 300px)"
+    >
+      <!-- ============ 鈶� 鏃舵 ============ -->
+      <el-table-column label="鏃舵" align="center" width="96">
         <template #default="scope">
-          <b>{{ formatTimeSlot(scope.row.timeSlot) }}</b>
-          <div class="text-11px text-gray-400">{{ scope.row.timeSlot }}</div>
+          <b class="text-12px" :title="String(scope.row.timeSlot ?? '')">
+            {{ formatTimeSlot(scope.row.timeSlot) }}
+          </b>
         </template>
       </el-table-column>
 
-      <el-table-column label="鏅��-璧峰搴忓彿" align="center" min-width="120px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueSet"
-            :min="0"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="鏅��-涓婇檺" align="center" min-width="110px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueFull"
-            :min="1"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="鏅��-褰撳墠搴忓彿" align="center" min-width="115px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueNo"
-            :min="0"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
+      <!-- ============ 鈶� 姝e父棰勭害 ============ -->
+      <el-table-column label="姝e父棰勭害" align="center">
+        <el-table-column label="璧峰搴忓彿" align="center" width="90">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueSet"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="涓婇檺" align="center" width="80">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueFull"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="棰勫畾棰濆害" align="center" width="90">
+          <template #default="scope">
+            <span
+              class="qs-quota"
+              :class="{ 'qs-quota--zero': quotaOf(scope.row.queueSet, scope.row.queueFull) <= 0 }"
+              :title="`涓婇檺 ${scope.row.queueFull ?? 0} 鈭� 璧峰 ${scope.row.queueSet ?? 0}`"
+            >
+              {{ quotaOf(scope.row.queueSet, scope.row.queueFull) }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="褰撳墠搴忓彿" align="center" width="90">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueNo"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="鐘舵��" align="center" width="90">
+          <template #default="scope">
+            <el-tag v-if="isFull(scope.row)" type="danger" size="small">宸叉弧</el-tag>
+            <el-tag v-else type="success" size="small">鍙彇鍙�</el-tag>
+          </template>
+        </el-table-column>
       </el-table-column>
 
-      <el-table-column label="VIP-璧峰搴忓彿" align="center" min-width="115px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueVipSet"
-            :min="0"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="VIP-涓婇檺" align="center" min-width="105px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueVipFull"
-            :min="1"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
-      </el-table-column>
-      <el-table-column label="VIP-褰撳墠搴忓彿" align="center" min-width="110px">
-        <template #default="scope">
-          <el-input-number
-            v-model="scope.row.queueVipNo"
-            :min="0"
-            :controls="false"
-            size="small"
-            style="width: 100%"
-            @change="markChanged(scope.row)"
-          />
-        </template>
+      <!-- ============ 鈶� 棰勭暀 ============ -->
+      <el-table-column label="棰勭暀" align="center">
+        <el-table-column label="璧峰搴忓彿" align="center" width="90">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueVipSet"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="涓婇檺" align="center" width="80">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueVipFull"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="棰勫畾棰濆害" align="center" width="90">
+          <template #default="scope">
+            <span
+              class="qs-quota"
+              :class="{ 'qs-quota--zero': quotaOf(scope.row.queueVipSet, scope.row.queueVipFull) <= 0 }"
+              :title="`涓婇檺 ${scope.row.queueVipFull ?? 0} 鈭� 璧峰 ${scope.row.queueVipSet ?? 0}`"
+            >
+              {{ quotaOf(scope.row.queueVipSet, scope.row.queueVipFull) }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="褰撳墠搴忓彿" align="center" width="90">
+          <template #default="scope">
+            <el-input-number
+              v-model="scope.row.queueVipNo"
+              :min="0"
+              :controls="false"
+              size="small"
+              style="width: 100%"
+              @change="markChanged(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="鐘舵��" align="center" width="90">
+          <template #default="scope">
+            <el-tag v-if="isFullVip(scope.row)" type="danger" size="small">宸叉弧</el-tag>
+            <el-tag v-else type="success" size="small">鍙彇鍙�</el-tag>
+          </template>
+        </el-table-column>
       </el-table-column>
 
-      <el-table-column label="鐘舵��" align="center" width="90px">
-        <template #default="scope">
-          <el-tag v-if="isFull(scope.row)" type="danger" size="small">宸叉弧</el-tag>
-          <el-tag v-else type="success" size="small">鍙彇鍙�</el-tag>
-        </template>
-      </el-table-column>
-
-      <el-table-column label="鎿嶄綔" align="center" width="160px" fixed="right">
+      <!-- ============ 鈶� 鎿嶄綔 ============ -->
+      <el-table-column label="鎿嶄綔" align="center" width="160" fixed="right">
         <template #default="scope">
           <el-button
             link
             type="primary"
             :loading="savingId === scope.row.id"
+           
             @click="handleRowSave(scope.row)"
           >
             淇濆瓨
           </el-button>
-          <el-button link type="primary" @click="openForm('update', scope.row)">缂栬緫</el-button>
-          <el-button link type="danger" @click="handleDelete(scope.row)">鍒犻櫎</el-button>
+          <el-button link type="primary" @click="openForm('update', scope.row)">
+            缂栬緫
+          </el-button>
+          <el-button link type="danger" @click="handleDelete(scope.row)">
+            鍒犻櫎
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -174,13 +223,15 @@
           <el-input-number v-model="formData.queueSet" :min="0" class="w-full" />
         </el-form-item>
         <el-form-item label="鏅��-涓婇檺" prop="queueFull">
-          <el-input-number v-model="formData.queueFull" :min="1" class="w-full" />
+          <!-- 銆愭湰娆¢渶姹傘�戜笂闄愬厑璁哥瓑浜庤捣濮嬪簭鍙凤紙=璇ユ椂娈垫棤鍚嶉锛夛紝鏁� min 鏀逛负 0 -->
+          <el-input-number v-model="formData.queueFull" :min="0" class="w-full" />
         </el-form-item>
         <el-form-item label="VIP-璧峰搴忓彿" prop="queueVipSet">
           <el-input-number v-model="formData.queueVipSet" :min="0" class="w-full" />
         </el-form-item>
         <el-form-item label="VIP-涓婇檺" prop="queueVipFull">
-          <el-input-number v-model="formData.queueVipFull" :min="1" class="w-full" />
+          <!-- 銆愭湰娆¢渶姹傘�戝悓鏅�氫笂闄愶細鍏佽绛変簬璧峰搴忓彿 -->
+          <el-input-number v-model="formData.queueVipFull" :min="0" class="w-full" />
         </el-form-item>
         <el-form-item label="鏅��-褰撳墠搴忓彿">
           <el-input-number v-model="formData.queueNo" :min="0" class="w-full" />
@@ -256,8 +307,23 @@
     {
       validator: (_rule: any, value: any, callback: any) => {
         const set = formData.value.queueSet
-        if (value !== undefined && value !== null && set !== undefined && set !== null && value <= set) {
-          callback(new Error('涓婇檺蹇呴』澶т簬璧峰搴忓彿锛屽惁鍒欒鏃舵鏃犳硶鍙栧彿'))
+        // 銆愭湰娆¢渶姹傘�戜笂闄� = 璧峰搴忓彿 鏄�**鍚堟硶**鐨勶細琛ㄧず璇ユ椂娈垫病鏈夊悕棰濓紙棰勫畾棰濆害 0锛夛紝鍏佽淇濆瓨銆�
+        // 鍙湁銆屼笂闄� < 璧峰搴忓彿銆嶆墠鏄厤缃敊璇紙璇ユ椂娈垫案杩滄棤娉曞彇鍙凤級銆�
+        if (value !== undefined && value !== null && set !== undefined && set !== null && value < set) {
+          callback(new Error('涓婇檺涓嶈兘灏忎簬璧峰搴忓彿锛堢瓑浜庤〃绀鸿鏃舵娌℃湁鍚嶉锛�'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
+  ],
+  queueVipFull: [
+    {
+      validator: (_rule: any, value: any, callback: any) => {
+        const set = formData.value.queueVipSet
+        if (value !== undefined && value !== null && set !== undefined && set !== null && value < set) {
+          callback(new Error('棰勭暀涓婇檺涓嶈兘灏忎簬棰勭暀璧峰搴忓彿锛堢瓑浜庤〃绀洪鐣欐病鏈夊悕棰濓級'))
         } else {
           callback()
         }
@@ -306,15 +372,47 @@
   }
 }
 
-/** 璇ユ椂娈垫槸鍚﹀凡婊★紙褰撳墠搴忓彿 >= 涓婇檺锛� */
-const isFull = (row: QueueSequenceVO) =>
-  (row.queueNo ?? 0) >= (row.queueFull ?? 0) && (row.queueFull ?? 0) > 0
+/**
+ * 棰勫畾棰濆害 = 涓婇檺 鈭� 璧峰搴忓彿锛堝皬浜� 0 鎸� 0 鏄剧ず锛夈��
+ *
+ * 銆愭湰娆¢渶姹傘�戣鍒楀彧璇伙紝鍊间负 0 鏃跺崟鍏冩牸娴呯孩搴� 鈥斺�� 琛ㄧず杩欎釜鏃舵**娌℃湁鍚嶉**銆�
+ * 銆屼笂闄� = 璧峰搴忓彿銆嶆槸鍚堟硶閰嶇疆锛堟棤鍚嶉鏃舵锛夛紝涓嶅啀褰撲綔閿欒銆�
+ */
+const quotaOf = (set?: number | null, full?: number | null) =>
+  Math.max(0, (full ?? 0) - (set ?? 0))
+
+/**
+ * 璇ユ椂娈垫槸鍚﹀凡婊★紙姝e父棰勭害锛夈��
+ *
+ * 婊$殑涓ょ鎯呭舰锛�
+ *  鈶� 棰勫畾棰濆害 <= 0锛堜笂闄� <= 璧峰搴忓彿锛夆啋 鏈潵灏辨病鏈夊悕棰濓紱
+ *  鈶� 褰撳墠搴忓彿宸茶揪鍒颁笂闄愩��
+ */
+const isFull = (row: QueueSequenceVO) => {
+  if (quotaOf(row.queueSet, row.queueFull) <= 0) return true
+  return (row.queueNo ?? 0) >= (row.queueFull ?? 0)
+}
+
+/** 璇ユ椂娈垫槸鍚﹀凡婊★紙棰勭暀缁勶級锛屽彛寰勫悓涓� */
+const isFullVip = (row: QueueSequenceVO) => {
+  if (quotaOf(row.queueVipSet, row.queueVipFull) <= 0) return true
+  return (row.queueVipNo ?? 0) >= (row.queueVipFull ?? 0)
+}
 
 /** 琛屽唴淇濆瓨锛堝彧鏀规暟鍊硷紝涓嶅姩鏃舵锛� */
 const handleRowSave = async (row: QueueSequenceVO) => {
   if (!row.id) return
-  if ((row.queueFull ?? 0) <= (row.queueSet ?? 0)) {
-    message.error(`鏃舵 ${formatTimeSlot(row.timeSlot)}锛氫笂闄愬繀椤诲ぇ浜庤捣濮嬪簭鍙穈)
+  // 銆愭湰娆¢渶姹傘�戝彧鏈夈�屼笂闄� < 璧峰搴忓彿銆嶆墠鏄敊璇紱鐩哥瓑琛ㄧず璇ユ椂娈垫病鏈夊悕棰濓紝鍏佽淇濆瓨銆�
+  if ((row.queueFull ?? 0) < (row.queueSet ?? 0)) {
+    message.error(
+      `鏃舵 ${formatTimeSlot(row.timeSlot)}锛氫笂闄愪笉鑳藉皬浜庤捣濮嬪簭鍙凤紙绛変簬琛ㄧず璇ユ椂娈垫病鏈夊悕棰濓級`
+    )
+    return
+  }
+  if ((row.queueVipFull ?? 0) < (row.queueVipSet ?? 0)) {
+    message.error(
+      `鏃舵 ${formatTimeSlot(row.timeSlot)}锛氶鐣欎笂闄愪笉鑳藉皬浜庨鐣欒捣濮嬪簭鍙凤紙绛変簬琛ㄧず棰勭暀娌℃湁鍚嶉锛塦
+    )
     return
   }
   savingId.value = row.id
@@ -388,3 +486,35 @@
   await getList()
 }
 </script>
+
+<style scoped lang="scss">
+/*
+  銆愭湰娆¤皟鏁淬�戝瓧鍙锋仮澶嶄负绯荤粺榛樿锛�**鍏佽妯悜婊氬姩**锛�
+    路 涓嶅啀鍘嬬缉瀛楀彿涓庡唴杈硅窛 鈥斺�� 琛ㄤ綋鎭㈠榛樿瀛楀彿锛�14px锛夈�佽緭鍏ユ榛樿瀛楀彿銆佺姸鎬佹爣绛剧敤 small 榛樿鏍峰紡锛�
+    路 鍒楀鐢辨ā鏉垮浐瀹氾紙鏃舵 96 + 姝e父棰勭害 5 鍒� + 棰勭暀 5 鍒� + 鎿嶄綔 160锛夛紱
+      鎬诲瓒呰繃寮圭獥瀹藉害鏃惰〃鏍煎嚭鐜�**妯悜婊氬姩鏉�**锛屾搷浣滃垪 fixed="right" 濮嬬粓鍙锛�
+    路 琛ㄦ牸楂樺害 calc(100vh - 300px)锛氱珫鍚戝鏄剧ず鍑犺锛岄〉闈㈡湰韬笉鍑虹幇婊氬姩鏉°��
+*/
+.qs-table {
+  /* 琛屽唴鏁板瓧杈撳叆鍗犳弧鍗曞厓鏍硷紙涓嶇缉灏忓瓧鍙凤紝鍙帶鍒跺搴︼級 */
+  :deep(.el-input-number) {
+    width: 100%;
+  }
+}
+
+/* 棰勫畾棰濆害锛堝彧璇伙級锛氬�间负 0 鏃舵祬绾㈠簳锛岃〃绀鸿鏃舵娌℃湁鍚嶉 */
+.qs-quota {
+  display: inline-block;
+  padding: 1px 6px;
+  border-radius: 3px;
+  font-weight: 600;
+  font-variant-numeric: tabular-nums;
+  color: var(--el-text-color-primary);
+}
+
+.qs-quota--zero {
+  color: var(--el-color-danger);
+  background-color: var(--el-color-danger-light-9);
+  box-shadow: inset 0 0 0 1px var(--el-color-danger-light-5);
+}
+</style>
\ No newline at end of file
diff --git a/src/views/ecg/doctor/DevInstall.vue b/src/views/ecg/doctor/DevInstall.vue
index 0e5a846..49e03f2 100644
--- a/src/views/ecg/doctor/DevInstall.vue
+++ b/src/views/ecg/doctor/DevInstall.vue
@@ -9,8 +9,9 @@
 import QueuePanel from "@/views/ecg/doctor/components/QueuePanel.vue";
 import DoctorPageLayout from "@/components/DoctorPageLayout/src/DoctorPageLayout.vue";
 import {DoctorApi, PatientStatisticVO, RoomBedVO} from '@/api/ecg/doctor';
+import { notifySeatInvalidOnce, isSeatInvalidError, resetSeatInvalidNotify } from '@/utils/seatGuard'
 import {QueueVO} from "@/api/ecg/queue";
-import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
+import {ElMessage, ElMessageBox} from "element-plus";
 import {useRoomStore} from "@/store/modules/room";
 
 const roomStore = useRoomStore();
@@ -89,11 +90,9 @@
 
 const initLoad = async () => {
   if (roomBedVO.value.roomId == null) {
-    ElNotification.error({
-      message: '璇烽噸鏂板氨搴�!',
-      type: 'info',
-      duration: 3000 // 鑷姩鍏抽棴鏃堕棿锛岄粯璁や负3000ms
-    })
+    // 銆愭湰闇�姹傘�戞病鏈夊伐浣嶏細鍋滄帀杞骞跺彧鎻愮ず涓�娆★紙鍘熸潵姣� 5 绉掑脊涓�娆★紝鐜板満琛ㄧ幇灏辨槸涓�鐩磋烦锛�
+    timerRunFlag = false
+    notifySeatInvalidOnce()
     return
   }
 
@@ -102,7 +101,19 @@
   fetchInfo.value = `鏈�杩戝彇鏁� ${new Date().toLocaleTimeString()} 路 杩斿洖 ${patientList.value.length} 鏉
   getInstallingPatient()
   patientStat.value = await DoctorApi.getDevInstallStatistic(roomBedVO.value)
-  const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO.value)
+  // 銆愭湰闇�姹傘�慴ed-doctor-get 鏄�愭垜鏄惁杩樺潗鍦ㄨ繖閲屻�戠殑鍒ゅ畾锛氬け璐ヨ鏄庡伐浣嶅凡琚噴鏀� / 浠栦汉鎺ユ墜锛�
+  // 姝ゆ椂鍋滄帀杞骞朵竴娆℃�ф彁绀猴紝閬垮厤姣� 5 绉掑埛涓�娆°�愯閲嶆柊灏卞骇銆戙��
+  let queueVO2: any
+  try {
+    queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO.value)
+  } catch (e: any) {
+    if (isSeatInvalidError(e)) {
+      timerRunFlag = false
+      notifySeatInvalidOnce()
+      return
+    }
+    throw e
+  }
   // 銆愬姞鍥恒�戞帴鍙e彲鑳借繑鍥� null锛屽師鏉ョ洿鎺ュ彇 .status 浼氭姏寮傚父銆佷腑鏂湰杞悗缁楠�
   if (queueVO2 && queueVO2.status === 20)
     state.value = true
@@ -239,6 +250,8 @@
     roomBedVO.value.bedNo = roomStore.room.bedNo
     roomBedVO.value.checkTypes = roomStore.room.checkTypes
     roomBedVO.value.opType = roomStore.room.opType
+    // 銆愭湰闇�姹傘�戦噸鏂版嬁鍒板伐浣� -> 澶嶄綅銆愬伐浣嶅け鏁堛�戞彁绀猴紝鍏佽涓嬫鍐嶆彁绀轰竴娆�
+    resetSeatInvalidNotify()
     // 銆愭湰娆¢渶姹傘�戞崲宸ヤ綅鍚�**鏁撮〉鏁版嵁閮芥崲鎴愭柊宸ヤ綅鐨�**锛�
     // 鈶� 鍏堟竻绌哄乏渚�"鎮h�呬俊鎭�"锛堥伩鍏嶈繕鏄剧ず涓婁竴涓伐浣嶇殑鎮h�咃級
     devInstallPanelRef.value?.setPatient?.(undefined)
@@ -266,6 +279,7 @@
     roomBedVO.value.bedNo = roomStore.room!.bedNo
     roomBedVO.value.checkTypes = roomStore.room!.checkTypes
     roomBedVO.value.opType = roomStore.room!.opType
+    resetSeatInvalidNotify()
 
     timerRunFlag = true
     doctorTimer()
diff --git a/src/views/ecg/doctor/Routine.vue b/src/views/ecg/doctor/Routine.vue
index 5545c0b..a0baaba 100644
--- a/src/views/ecg/doctor/Routine.vue
+++ b/src/views/ecg/doctor/Routine.vue
@@ -9,8 +9,9 @@
 import QueuePanel from '@/views/ecg/doctor/components/QueuePanel.vue'
 import DoctorPageLayout from '@/components/DoctorPageLayout/src/DoctorPageLayout.vue'
 import { DoctorApi, PatientStatisticVO, RoomBedVO, } from '@/api/ecg/doctor'
+import { notifySeatInvalidOnce, isSeatInvalidError, resetSeatInvalidNotify } from '@/utils/seatGuard'
 import { QueueVO } from '@/api/ecg/queue'
-import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import RoutinePanel from '@/views/ecg/doctor/components/RoutinePanel.vue'
 import { useRoomStore } from '@/store/modules/room'
 
@@ -303,11 +304,9 @@
 
 const initLoad = async () => {
   if (roomBedVO.roomId == null) {
-    ElNotification.error({
-      message: '璇烽噸鏂板氨搴�!',
-      type: 'info',
-      duration: 3000 // 鑷姩鍏抽棴鏃堕棿锛岄粯璁や负3000ms
-    })
+    // 銆愭湰闇�姹傘�戞病鏈夊伐浣嶏細**鍋滄帀杞**骞跺彧鎻愮ず涓�娆★紙鍘熸潵姣� 5 绉掑脊涓�娆★紝鐜板満琛ㄧ幇灏辨槸"涓�鐩磋烦"锛�
+    timerRunFlag = false
+    notifySeatInvalidOnce()
     return
   }
 
@@ -318,7 +317,19 @@
   loadRoomWaitingList()
   getOnstagePatient()
   patientStat.value = await DoctorApi.getPatientStatistic(roomBedVO)
-  const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO)
+  // 銆愭湰闇�姹傘�慴ed-doctor-get 鏄�"鎴戞槸鍚﹁繕鍧愬湪杩欓噷"鐨勫垽瀹氾細澶辫触璇存槑宸ヤ綅宸茶閲婃斁 / 浠栦汉鎺ユ墜锛�
+  // 姝ゆ椂鍋滄帀杞骞朵竴娆℃�ф彁绀猴紝閬垮厤姣� 5 绉掑埛涓�娆�"璇烽噸鏂板氨搴�"銆�
+  let queueVO2: any
+  try {
+    queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO)
+  } catch (e: any) {
+    if (isSeatInvalidError(e)) {
+      timerRunFlag = false
+      notifySeatInvalidOnce()
+      return
+    }
+    throw e
+  }
   // 銆愬姞鍥恒�戞帴鍙e彲鑳借繑鍥� null锛屽師鏉ョ洿鎺ュ彇 .status 浼氭姏寮傚父銆佷腑鏂湰杞悗缁楠�
   if (queueVO2 && queueVO2.status === 20) state.value = true
   else if (queueVO2 && queueVO2.status === 30) state.value = false
@@ -366,6 +377,8 @@
     roomBedVO.bedNo = roomStore.room.bedNo
     roomBedVO.checkTypes = roomStore.room.checkTypes
     roomBedVO.opType = roomStore.room.opType
+    // 銆愭湰闇�姹傘�戦噸鏂版嬁鍒板伐浣� -> 澶嶄綅銆愬伐浣嶅け鏁堛�戞彁绀猴紝鍏佽涓嬫鍐嶆彁绀轰竴娆�
+    resetSeatInvalidNotify()
     // 銆愭湰娆¢渶姹傘�戞崲宸ヤ綅鍚�**鏁撮〉鏁版嵁閮芥崲鎴愭柊宸ヤ綅鐨�**锛�
     // 鈶� 鍏堟竻绌哄乏渚�"鎮h�呬俊鎭�"锛堥伩鍏嶈繕鏄剧ず涓婁竴涓伐浣嶇殑鎮h�咃級
     routimePanelRef.value?.setPatient?.(undefined)
@@ -394,6 +407,7 @@
     roomBedVO.bedNo = roomStore.room!.bedNo
     roomBedVO.checkTypes = roomStore.room!.checkTypes
     roomBedVO.opType = roomStore.room!.opType
+    resetSeatInvalidNotify()
 
     timerRunFlag = true
     doctorTimer()
diff --git a/src/views/ecg/doctor/index.vue b/src/views/ecg/doctor/index.vue
index 1091031..2c3ddeb 100644
--- a/src/views/ecg/doctor/index.vue
+++ b/src/views/ecg/doctor/index.vue
@@ -10,8 +10,9 @@
 import QueuePanel from "@/views/ecg/doctor/components/QueuePanel.vue";
 import DoctorPageLayout from "@/components/DoctorPageLayout/src/DoctorPageLayout.vue";
 import {DoctorApi, PatientStatisticVO, RoomBedVO} from '@/api/ecg/doctor';
+import { notifySeatInvalidOnce, isSeatInvalidError, resetSeatInvalidNotify } from '@/utils/seatGuard'
 import {QueueVO} from "@/api/ecg/queue";
-import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
+import {ElMessage, ElMessageBox} from "element-plus";
 import DevReadyPanel from "@/views/ecg/doctor/components/DevReadyPanel.vue";
 import {useRoomStore} from "@/store/modules/room";
 
@@ -243,11 +244,9 @@
 
 const initLoad = async () => {
   if (roomBedVO.roomId == null) {
-    ElNotification.error({
-      message: '璇烽噸鏂板氨搴�!',
-      type: 'info',
-      duration: 3000 // 鑷姩鍏抽棴鏃堕棿锛岄粯璁や负3000ms
-    })
+    // 銆愭湰闇�姹傘�戞病鏈夊伐浣嶏細鍋滄帀杞骞跺彧鎻愮ず涓�娆★紙鍘熸潵姣� 5 绉掑脊涓�娆★紝鐜板満琛ㄧ幇灏辨槸涓�鐩磋烦锛�
+    timerRunFlag = false
+    notifySeatInvalidOnce()
     return
   }
 
@@ -258,7 +257,19 @@
   loadRoomWaitingList()
   getOnstagePatient()
   patientStat.value = await DoctorApi.getDevReadyStatistic(roomBedVO)
-  const queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO)
+  // 銆愭湰闇�姹傘�慴ed-doctor-get 鏄�愭垜鏄惁杩樺潗鍦ㄨ繖閲屻�戠殑鍒ゅ畾锛氬け璐ヨ鏄庡伐浣嶅凡琚噴鏀� / 浠栦汉鎺ユ墜锛�
+  // 姝ゆ椂鍋滄帀杞骞朵竴娆℃�ф彁绀猴紝閬垮厤姣� 5 绉掑埛涓�娆°�愯閲嶆柊灏卞骇銆戙��
+  let queueVO2: any
+  try {
+    queueVO2 = await DoctorApi.bedDoctorGet(roomBedVO)
+  } catch (e: any) {
+    if (isSeatInvalidError(e)) {
+      timerRunFlag = false
+      notifySeatInvalidOnce()
+      return
+    }
+    throw e
+  }
   // 銆愬姞鍥恒�戞帴鍙e彲鑳借繑鍥� null锛屽師鏉ョ洿鎺ュ彇 .status 浼氭姏寮傚父銆佷腑鏂湰杞悗缁楠�
   if (queueVO2 && queueVO2.status === 20)  // 宸ヤ綅鐘舵�� 鏈夊尰鐢�
     state.value = true
@@ -305,6 +316,8 @@
     roomBedVO.bedNo = roomStore.room.bedNo
     roomBedVO.checkTypes = roomStore.room.checkTypes
     roomBedVO.opType = roomStore.room.opType
+    // 銆愭湰闇�姹傘�戦噸鏂版嬁鍒板伐浣� -> 澶嶄綅銆愬伐浣嶅け鏁堛�戞彁绀猴紝鍏佽涓嬫鍐嶆彁绀轰竴娆�
+    resetSeatInvalidNotify()
     // 銆愭湰娆¢渶姹傘�戞崲宸ヤ綅鍚�**鏁撮〉鏁版嵁閮芥崲鎴愭柊宸ヤ綅鐨�**锛�
     // 鈶� 鍏堟竻绌哄乏渚�"鎮h�呬俊鎭�"锛堥伩鍏嶈繕鏄剧ず涓婁竴涓伐浣嶇殑鎮h�咃級
     devReadyPanelRef.value?.setPatient?.(undefined)
@@ -333,6 +346,7 @@
     roomBedVO.bedNo = roomStore.room!.bedNo
     roomBedVO.checkTypes = roomStore.room!.checkTypes
     roomBedVO.opType = roomStore.room!.opType
+    resetSeatInvalidNotify()
 
     timerRunFlag = true
     doctorTimer()
diff --git a/src/views/ecg/room/index.vue b/src/views/ecg/room/index.vue
index adac25a..37d8fe0 100644
--- a/src/views/ecg/room/index.vue
+++ b/src/views/ecg/room/index.vue
@@ -104,6 +104,31 @@
           >
             鍒犻櫎
           </el-button>
+          <!--
+            銆愭湰娆¢渶姹傘�戠鐞嗗憳寮哄埗娓呯┖宸ヤ綅锛氬尰鐢熶笅鐝繕绂诲骇 / 鎹汉浣跨敤鍚屼竴宸ヤ綅鏃剁敤銆�
+
+            路 鏉冮檺锛歚v-hasPermi` 鏄�"浠讳竴鍛戒腑鍗虫樉绀�"锛岃繖閲屽悓鏃舵帴鍙� `clinic:room:update`锛堝悗绔帴鍙h姹傜殑锛�
+              涓� `ecg:room:update`锛堟湰椤靛叾瀹冩寜閽敤鐨勯偅濂楋級锛岄伩鍏嶈鑹插彧閰嶄簡鍏朵腑涓�濂楁椂鎸夐挳琚殣钘忋��
+              鍚庣浠嶄弗鏍兼牎楠� `clinic:room:update`銆�
+            路 鍙湁"宸茶鍗犵敤"锛坉ocId 鏈夊�硷級鐨勫伐浣嶆墠鍙偣锛涚┖闂插伐浣嶇疆鐏板苟鎻愮ず锛岄伩鍏嶇偣浜嗘墠鎶�"鐘舵�佷笉鍏佽"銆�
+          -->
+          <el-tooltip
+            :disabled="!!scope.row.docId"
+            content="璇ュ伐浣嶅綋鍓嶇┖闂诧紝鏃犻渶娓呯┖"
+            placement="top"
+          >
+            <span style="margin-left: 12px">
+              <el-button
+                link
+                type="warning"
+                :disabled="!scope.row.docId"
+                @click="handleForceClearBed(scope.row)"
+                v-hasPermi="['clinic:room:update', 'ecg:room:update']"
+              >
+                娓呯┖宸ヤ綅
+              </el-button>
+            </span>
+          </el-tooltip>
         </template>
       </el-table-column>
     </el-table>
@@ -207,8 +232,29 @@
   } catch {}
 }
 
-/** 瀵煎嚭鎸夐挳鎿嶄綔 */
-const handleExport = async () => {
+/**
+ * 銆愭湰娆¢渶姹傘�戠鐞嗗憳寮哄埗娓呯┖宸ヤ綅銆�
+ *
+ * 鐢ㄩ�旓細鍖荤敓涓嬬彮蹇樿銆岀搴с�嶁啋 宸ヤ綅涓�鐩磋鍗犵敤锛屽埆鐨勫尰鐢熷潗涓嶄笂锛�
+ * 鎴栨崲浜轰娇鐢ㄥ悓涓�宸ヤ綅鏃堕渶瑕佹妸涓婁竴浣嶆竻鎺夈��
+ *
+ * 鏈嶅姟绔彧鍦ㄨ宸ヤ綅澶勪簬銆屽尰鐢熷湪宀� / 鏆傚仠銆嶄笖宸茶鍗犵敤鏃舵墠鍏佽娓呯┖锛�
+ * 绌洪棽宸ヤ綅浼氳繑鍥炪�屽綋鍓嶅伐浣嶇姸鎬佷笉鍏佽璇ユ搷浣溿�嶁�斺�旇繖鏄鏈熻涓猴紝鏃犻渶鐗规畩澶勭悊銆�
+ */
+const handleForceClearBed = async (row: RoomVO) => {
+  const who = row.docName ? `銆�${row.docName}銆峘 : '褰撳墠鍖荤敓'
+  try {
+    await message.confirm(
+      `纭娓呯┖宸ヤ綅銆�${row.roomName ?? ''} 路 ${row.bedNo ?? ''}銆嶅悧锛焅n` +
+        `璇ュ伐浣嶅綋鍓嶇敱${who}鍗犵敤锛屾竻绌哄悗瀵规柟灏嗗彉鎴�"浠呭洿瑙�"锛岄渶瑕侀噸鏂板叆搴ф墠鑳芥搷浣溿�俙
+    )
+    await RoomApi.forceClearBed({ roomId: row.roomId, bedNo: row.bedNo })
+    message.success('宸叉竻绌鸿宸ヤ綅')
+    await getList()
+  } catch {}
+}
+
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */const handleExport = async () => {
   try {
     // 瀵煎嚭鐨勪簩娆$‘璁�
     await message.exportConfirm()

--
Gitblit v1.9.3