From 91f78c7a3c325b7627f269524cdf92f006948cdf Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期一, 20 十月 2025 17:37:35 +0800
Subject: [PATCH] 景宁电话接入

---
 src/views/followvisit/discharge/ClickCall.vue |  622 +++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 453 insertions(+), 169 deletions(-)

diff --git a/src/views/followvisit/discharge/ClickCall.vue b/src/views/followvisit/discharge/ClickCall.vue
index 2896d7b..727d67e 100644
--- a/src/views/followvisit/discharge/ClickCall.vue
+++ b/src/views/followvisit/discharge/ClickCall.vue
@@ -1,21 +1,36 @@
 <template>
   <div class="websocket-demo">
     <div>
-      <h3>Websocket鎺ュ彛娴嬭瘯DEMO</h3>
+      <h3>Websocket鍛煎彨涓績鎺ュ彛</h3>
       <div class="config-area">
+        <div class="status-indicator">
+          <span :class="['status-dot', connectionStatus]"></span>
+          杩炴帴鐘舵��: {{ connectionText }}
+          <span :class="['status-dot', seatStatus]"></span>
+          搴у腑鐘舵��: {{ seatStatusText }}
+        </div>
+
         <div class="input-group">
           <label>CTI_WS_URL</label>
           <input
             type="text"
             v-model="config.cti_ws_url"
-            placeholder="ws://40.78.0.169:6688"
+            placeholder="wss://your-server.com"
           />
 
           <label>鍧愬腑宸ュ彿</label>
-          <input type="text" v-model="config.seatname" placeholder="8000" />
+          <input
+            type="text"
+            v-model="config.seatname"
+            :placeholder="randomNum"
+          />
 
           <label>鍧愬腑鍒嗘満</label>
-          <input type="text" v-model="config.seatnum" placeholder="8000" />
+          <input
+            type="text"
+            v-model="config.seatnum"
+            :placeholder="randomNum"
+          />
 
           <label>瀵嗙爜</label>
           <input type="text" v-model="config.password" placeholder="123456" />
@@ -23,129 +38,136 @@
 
         <div class="input-group">
           <label>澶栫嚎鍙风爜</label>
-          <input type="text" v-model="config.phone" placeholder="10086" />
-
-          <label>UUID</label>
-          <input type="text" v-model="config.uuid" />
-
-          <label>鍏朵粬鍧愬腑</label>
-          <input type="text" v-model="config.other" placeholder="8001" />
+          <input
+            type="text"
+            v-model="customerPhone"
+            placeholder="璇疯緭鍏ョ數璇濆彿鐮�"
+          />
 
           <label>鎶�鑳界粍</label>
           <input type="text" v-model="config.group" placeholder="a3" />
-
-          <label>澶栧懠鍙傛暟id</label>
-          <input type="text" v-model="config.paramid" placeholder="3" />
         </div>
       </div>
 
       <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
       <div class="button-area">
-        <!-- 绗竴琛屾寜閽� -->
         <div class="button-row">
-          <button @click="seatlogin">绛惧叆</button>
-          <button @click="seatlogout">绛惧嚭</button>
-          <button @click="afk">绀哄繖</button>
-          <button @click="online">绀洪棽</button>
-          <button @click="pickup">浠g瓟</button>
+          <button
+            @click="handleSeatLogin"
+            :disabled="!isConnected || isSeatLoggedIn"
+          >
+            绛惧叆
+          </button>
+          <button @click="handleSeatLogout" :disabled="!isSeatLoggedIn">
+            绛惧嚭
+          </button>
+          <button @click="callout" :disabled="!isSeatLoggedIn">澶栧懠</button>
+          <button @click="hangup" :disabled="!isSeatLoggedIn">鎸傛満</button>
         </div>
 
-        <!-- 绗簩琛屾寜閽� -->
         <div class="button-row">
-          <button @click="hangup">鎸傛満</button>
-          <button @click="callout">澶栧懠</button>
-          <button @click="transfer">閫氳瘽杞Щ</button>
-          <button @click="transferresume">閫氳瘽杞Щ鏀跺洖</button>
-          <button @click="hold">閫氳瘽淇濇寔</button>
-          <button @click="holdresume">閫氳瘽淇濇寔鏀跺洖</button>
-          <button @click="remove">閫氳瘽寮烘媶</button>
-          <button @click="insert">閫氳瘽寮烘彃</button>
-          <button @click="monitor">鐩戝惉</button>
-          <button @click="monitor_to_talk">鐩戝惉杞�氳瘽</button>
-          <button @click="monitor_end">鐩戝惉缁撴潫</button>
-          <button @click="choosecall">閫夋嫨</button>
-          <button @click="replacecall">浠f帴</button>
-          <button @click="three">涓夋柟閫氳瘽</button>
-        </div>
-
-        <!-- 绗笁琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="handoff_ready">鍜ㄨ寮�濮�</button>
-          <button @click="handoff_call">鍜ㄨ鍛煎彨</button>
-          <button @click="handoff_resume">鍜ㄨ鏀跺洖</button>
-          <button @click="handoff_transfer">鍜ㄨ杞Щ</button>
-          <button @click="handoff_three">鍜ㄨ涓夋柟</button>
-          <button @click="record_start">寮�濮嬮�氳瘽褰曢煶</button>
-          <button @click="record_stop">鍋滄閫氳瘽褰曢煶</button>
-        </div>
-
-        <!-- 绗洓琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="openseatlist">鎵撳紑鍧愬腑鐘舵��</button>
-          <button @click="closeseatlist">鍏抽棴鍧愬腑鐘舵��</button>
-          <button @click="openqueues">鎵撳紑闃熷垪淇℃伅</button>
-          <button @click="closequeues">鍏抽棴闃熷垪淇℃伅</button>
-          <button @click="opencalllist">鎵撳紑閫氳瘽淇℃伅</button>
-          <button @click="closecalllist">鍏抽棴閫氳瘽淇℃伅</button>
-          <button @click="openroutelist">鎵撳紑璺敱淇℃伅</button>
-          <button @click="closeroutelist">鍏抽棴璺敱淇℃伅</button>
-        </div>
-
-        <!-- 绗簲琛屾寜閽� -->
-        <div class="button-row">
-          <button @click="seatlist">鑾峰彇鍧愬腑淇℃伅</button>
-          <button @click="queues">鑾峰彇闃熷垪淇℃伅</button>
-          <button @click="calllist">鑾峰彇閫氳瘽淇℃伅</button>
-          <button @click="routelist">鑾峰彇璺敱淇℃伅</button>
-          <button @click="batch">鑾峰彇澶栧懠鍙傛暟淇℃伅</button>
-          <button @click="batch_start">寮�濮嬪鍛间换鍔�</button>
-          <button @click="batch_stop">鍋滄澶栧懠浠诲姟</button>
+          <button @click="afk" :disabled="!isSeatLoggedIn">绀哄繖</button>
+          <button @click="online" :disabled="!isSeatLoggedIn">绀洪棽</button>
+          <button @click="hold" :disabled="!isSeatLoggedIn">淇濇寔</button>
+          <button @click="holdresume" :disabled="!isSeatLoggedIn">
+            鍙栨秷淇濇寔
+          </button>
         </div>
       </div>
 
       <!-- 鏃ュ織鏄剧ず鍖哄煙 -->
-      <h3>鍗忚鏃ュ織鍖�<button @click="testclear">娓呴櫎</button></h3>
-      <div id="msg" class="log-area">{{ logs }}</div>
+      <h3>鍗忚鏃ュ織鍖� <button @click="testclear">娓呴櫎</button></h3>
+      <div class="log-area">{{ logs }}</div>
     </div>
   </div>
 </template>
 
 <script>
+import { CallsetState, CallgetList } from "@/api/AiCentre/index";
+
 export default {
   name: "WebsocketDemo",
+  emits: ["status-change", "call-status", "error"],
+
+  props: {
+    customerPhone: {
+      type: String,
+      default: "",
+    },
+    autoLogin: {
+      type: Boolean,
+      default: true,
+    },
+  },
 
   data() {
     return {
       config: {
-        cti_ws_url: "wss://9.208.2.190:8092/cal-api/",
-        seatname: "8000",
-        seatnum: "8000",
+        cti_ws_url: "",
+        seatname: "",
+        seatnum: "",
         password: "123456",
-        phone: "10086",
+        phone: "",
         uuid: "",
         other: "8001",
         group: "a3",
         paramid: "3",
       },
+      randomNum: "",
+      randomID: "",
       logs: "",
       ws: null,
       isConnected: false,
+      isSeatLoggedIn: false,
+      currentCallStatus: "idle", // idle, calling, connected
+      seatResourceAcquired: false,
+      reconnectAttempts: 0,
+      maxReconnectAttempts: 5,
+      heartbeatTimer: null,
     };
   },
 
-  mounted() {
+  computed: {
+    connectionStatus() {
+      return this.isConnected ? "connected" : "disconnected";
+    },
+    connectionText() {
+      return this.isConnected ? "宸茶繛鎺�" : "鏈繛鎺�";
+    },
+    seatStatus() {
+      return this.isSeatLoggedIn ? "logged-in" : "logged-out";
+    },
+    seatStatusText() {
+      return this.isSeatLoggedIn ? "宸茬鍏�" : "鏈鍏�";
+    },
+  },
+
+  watch: {
+    customerPhone(newVal) {
+      this.config.phone = newVal;
+    },
+    isSeatLoggedIn(newVal) {
+      this.$emit("status-change", {
+        isLoggedIn: newVal,
+        seatNumber: this.config.seatnum,
+        status: newVal ? "ready" : "offline",
+      });
+    },
+  },
+
+  async mounted() {
+    await this.initializeSeatResource();
     this.initializeWebSocket();
   },
 
   beforeUnmount() {
-    this.disconnectWebSocket();
+    this.cleanup();
   },
 
   methods: {
     // 鍒濆鍖朩ebSocket杩炴帴
     initializeWebSocket() {
       try {
-        // 鏍规嵁褰撳墠椤甸潰鍗忚鑷姩閫夋嫨WS鍗忚
         const isHttps = window.location.protocol === "https:";
         this.config.cti_ws_url = isHttps
           ? "wss://9.208.2.190:8092/cal-api/"
@@ -159,22 +181,70 @@
         this.connectWebSocket();
       } catch (error) {
         this.addLog(`鍒濆鍖朩ebSocket閿欒: ${error.message}`);
-        // 灏濊瘯浣跨敤澶囩敤鍦板潃
-        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
         setTimeout(() => this.connectWebSocket(), 2000);
       }
     },
+    // 鍒濆鍖栧骇甯彿璧勬簮
+    async initializeSeatResource() {
+      try {
+        const res = await CallgetList();
+        if (res.data && res.data.length > 0) {
+          // this.randomNum = res.data[0].tel;
+          // this.randomID = res.data[0].id;
+          this.randomNum = 8000;
+          this.randomID = 8000;
+          // 璁剧疆榛樿搴у腑鍙�
+          this.config.seatname = this.randomNum;
+          this.config.seatnum = this.randomNum;
 
+          // 绔嬪嵆鍗犵敤搴у腑鍙疯祫婧�
+          await this.startCallsetState();
+          this.seatResourceAcquired = true;
+          this.addLog(`搴у腑鍙疯祫婧愯幏鍙栨垚鍔�: ${this.randomNum}`);
+        }
+      } catch (error) {
+        console.error("鑾峰彇搴у腑鍙峰け璐�:", error);
+        this.addLog("閿欒: 鑾峰彇搴у腑鍙疯祫婧愬け璐�");
+        this.$emit("error", { type: "seat_acquisition_failed", error });
+      }
+    },
+    // 鍗犵敤搴у腑鍙�
+    async startCallsetState() {
+      try {
+        await CallsetState({ id: this.randomID, state: 1 });
+        this.addLog("搴у腑鍙风姸鎬佹洿鏂颁负浣跨敤涓�");
+      } catch (error) {
+        console.error("鏇存柊搴у腑鍙风姸鎬佸け璐�:", error);
+        throw error;
+      }
+    },
+
+    // 閲婃斁搴у腑鍙�
+    async releaseSeatResource() {
+      if (this.seatResourceAcquired && this.randomID) {
+        try {
+          await CallsetState({ id: this.randomID, state: 0 });
+          this.addLog("搴у腑鍙疯祫婧愬凡閲婃斁");
+          this.seatResourceAcquired = false;
+        } catch (error) {
+          console.error("閲婃斁搴у腑鍙峰け璐�:", error);
+        }
+      }
+    },
+    // 杩炴帴WebSocket
     // 杩炴帴WebSocket
     connectWebSocket() {
       if (this.ws && this.ws.readyState === WebSocket.OPEN) {
-        this.addLog("WebSocket宸茶繛鎺�");
+        return;
+      }
+
+      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
+        this.addLog("閿欒: 杈惧埌鏈�澶ч噸杩炴鏁帮紝鍋滄閲嶈繛");
         return;
       }
 
       try {
         let wsUrl = this.config.cti_ws_url;
-        // 纭繚HTTPS椤甸潰浣跨敤WSS
         if (
           window.location.protocol === "https:" &&
           wsUrl.startsWith("ws://")
@@ -186,7 +256,14 @@
 
         this.ws.onopen = () => {
           this.isConnected = true;
+          this.reconnectAttempts = 0;
           this.addLog("WebSocket杩炴帴鎴愬姛");
+          this.startHeartbeat();
+
+          // 杩炴帴鎴愬姛鍚庤嚜鍔ㄧ鍏�
+          if (this.autoLogin && this.seatResourceAcquired) {
+            setTimeout(() => this.handleSeatLogin(), 500);
+          }
         };
 
         this.ws.onmessage = (event) => {
@@ -195,54 +272,202 @@
 
         this.ws.onclose = (event) => {
           this.isConnected = false;
+          this.isSeatLoggedIn = false;
+          this.stopHeartbeat();
           this.addLog(`WebSocket杩炴帴鍏抽棴: ${event.code} ${event.reason}`);
+
           // 鑷姩閲嶈繛
-          setTimeout(() => this.connectWebSocket(), 3000);
+          if (this.reconnectAttempts < this.maxReconnectAttempts) {
+            this.reconnectAttempts++;
+            setTimeout(() => this.connectWebSocket(), 3000);
+          }
         };
 
         this.ws.onerror = (error) => {
           this.addLog(`WebSocket閿欒: ${error.message}`);
-          // 灏濊瘯澶囩敤URL
-          if (!wsUrl.includes("9.208.2.190")) {
-            this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
-            setTimeout(() => this.connectWebSocket(), 3000);
-          }
         };
       } catch (error) {
         this.addLog(`杩炴帴WebSocket澶辫触: ${error.message}`);
-        // 灏濊瘯澶囩敤URL
-        this.config.cti_ws_url = "wss://9.208.2.190:8092/cal-api/";
         setTimeout(() => this.connectWebSocket(), 3000);
       }
     },
 
     // 澶勭悊WebSocket娑堟伅
     handleWebSocketMessage(event) {
-      const reader = new FileReader();
-      reader.onloadend = (e) => {
-        const message = reader.result;
-        this.addLog(`鏀跺埌娑堟伅: ${message}`);
-
-        try {
-          const obj = JSON.parse(message);
-
-          // 澶勭悊蹇冭烦鍖�
-          if (obj.cmd === "system" && obj.action === "keepalive") {
-            this.keepalive(obj.seatname, obj.seatnum);
-          }
-
-          // 鑷姩璁剧疆UUID
-          if (obj.cmd === "control" && obj.action === "tp_callin") {
-            this.config.uuid = obj.uuid;
-            this.addLog(`鑷姩璁剧疆UUID: ${obj.uuid}`);
-          }
-        } catch (error) {
-          this.addLog(`娑堟伅瑙f瀽閿欒: ${error.message}`);
+      try {
+        // 妫�鏌ユ暟鎹被鍨嬶細鍙兘鏄瓧绗︿覆鎴朆lob
+        if (event.data instanceof Blob) {
+          // 澶勭悊浜岃繘鍒舵暟鎹紙Blob锛�
+          const reader = new FileReader();
+          reader.onload = () => {
+            try {
+              const textData = reader.result;
+              this.addLog(`鏀跺埌Blob娑堟伅: ${textData}`);
+              this.processWebSocketData(textData);
+            } catch (error) {
+              this.addLog(`Blob鏁版嵁澶勭悊閿欒: ${error.message}`);
+            }
+          };
+          reader.readAsText(event.data);
+        } else if (typeof event.data === "string") {
+          // 鐩存帴澶勭悊鏂囨湰鏁版嵁
+          this.addLog(`鏀跺埌鏂囨湰娑堟伅: ${event.data}`);
+          this.processWebSocketData(event.data);
+        } else {
+          this.addLog(`鏈煡鏁版嵁绫诲瀷: ${typeof event.data}`);
         }
+      } catch (error) {
+        this.addLog(`娑堟伅澶勭悊閿欒: ${error.message}`);
+      }
+    },
+    // 涓撻棬澶勭悊瑙f瀽鍚庣殑WebSocket鏁版嵁
+    processWebSocketData(messageText) {
+      console.log(messageText,'娑堟伅1');
+
+      try {
+        const obj = JSON.parse(messageText);
+      console.log(obj,'娑堟伅2');
+
+        // 澶勭悊蹇冭烦鍖�
+        if (obj.cmd === "system" && obj.action === "keepalive") {
+          this.keepalive(obj.seatname, obj.seatnum);
+        }
+        // 澶勭悊鎸傛柇
+         if (obj.action === "calloutend") {
+          this.hangup();
+        }
+
+        // 澶勭悊绛惧叆鍝嶅簲
+        if (obj.cmd === "system" && obj.action === "seatlogin") {
+            this.isSeatLoggedIn = true;
+            this.addLog("搴у腑绛惧叆鎴愬姛");
+            this.$emit("status-change", {
+              isLoggedIn: true,
+              seatNumber: this.config.seatnum,
+              status: "ready",
+            });
+        }
+
+        // 澶勭悊绛惧嚭鍝嶅簲
+        if (obj.cmd === "system" && obj.action === "seatlogout") {
+          this.isSeatLoggedIn = false;
+          this.addLog("搴у腑绛惧嚭鎴愬姛");
+          this.$emit("status-change", {
+            isLoggedIn: false,
+            status: "offline",
+          });
+        }
+
+        // 鑷姩璁剧疆UUID锛堟潵鐢典簨浠讹級
+        if (obj.cmd === "control" && obj.action === "tp_callin") {
+          this.config.uuid = obj.uuid;
+          this.addLog(`鑷姩璁剧疆UUID: ${obj.uuid}`);
+          this.$emit("call-status", {
+            status: "incoming",
+            uuid: obj.uuid,
+            phone: obj.phone || "鏈煡鍙风爜",
+          });
+        }
+
+        // 澶勭悊澶栧懠鍝嶅簲
+        if (obj.cmd === "control" && obj.action === "callout") {
+            this.$emit("call-status", {
+              status: obj.status || "calling",
+              uuid: obj.uuid,
+              phone: this.config.phone,
+            });
+        }
+
+        // 澶勭悊鎸傛満鍝嶅簲
+        if (obj.cmd === "control" && obj.action === "hangup") {
+          this.$emit("call-status", {
+            status: "idle",
+            uuid: obj.uuid,
+          });
+        }
+
+        // 澶勭悊閫氳瘽鐘舵�佸彉鍖�
+        if (obj.cmd === "control" && obj.status) {
+          this.handleCallStatusChange(obj);
+        }
+      } catch (error) {
+        this.addLog(`JSON瑙f瀽閿欒: ${error.message}, 鍘熷鏁版嵁: ${messageText}`);
+      }
+    },
+    // 澶勭悊鍛煎彨鐘舵�佸彉鍖�
+    handleCallStatusChange(obj) {
+      const statusMap = {
+        ringing: "鎸搩涓�",
+        connected: "閫氳瘽涓�",
+        held: "宸蹭繚鎸�",
+        ended: "閫氳瘽缁撴潫",
       };
-      reader.readAsText(event.data);
+
+      this.addLog(`閫氳瘽鐘舵��: ${statusMap[obj.status] || obj.status}`);
+      this.$emit("call-status", {
+        status: obj.status,
+        uuid: obj.uuid,
+        phone: obj.phone || this.config.phone,
+      });
+    },
+    // 寮�濮嬪績璺虫娴�
+    startHeartbeat() {
+      this.heartbeatTimer = setInterval(() => {
+        if (this.isConnected && this.isSeatLoggedIn) {
+          this.keepalive(this.config.seatname, this.config.seatnum);
+        }
+      }, 30000); // 30绉掑績璺�
     },
 
+    // 鍋滄蹇冭烦妫�娴�
+    stopHeartbeat() {
+      if (this.heartbeatTimer) {
+        clearInterval(this.heartbeatTimer);
+        this.heartbeatTimer = null;
+      }
+    },
+    // 搴у腑绛惧叆
+    async handleSeatLogin() {
+      if (!this.seatResourceAcquired) {
+        this.addLog("閿欒: 鏈幏鍙栧骇甯彿璧勬簮锛屾棤娉曠鍏�");
+        return;
+      }
+
+      const { seatname, seatnum, password } = this.config;
+      if (!seatname || !seatnum) {
+        this.addLog("閿欒: 搴у腑宸ュ彿鍜屽垎鏈哄彿涓嶈兘涓虹┖");
+        return;
+      }
+
+      const protocol = {
+        cmd: "system",
+        action: "seatlogin",
+        seatname: seatname,
+        seatnum: seatnum,
+        password: password,
+        timestamp: Date.now(),
+      };
+
+      this.sendWebSocketMessage(protocol);
+    },
+    // 搴у腑绛惧嚭
+    async handleSeatLogout() {
+      const { seatname, seatnum } = this.config;
+
+      const protocol = {
+        cmd: "system",
+        action: "seatlogout",
+        seatname: seatname,
+        seatnum: seatnum,
+        timestamp: Date.now(),
+      };
+
+      if (this.sendWebSocketMessage(protocol)) {
+        this.isSeatLoggedIn = false;
+        // 寤惰繜閲婃斁璧勬簮锛岀‘淇濈鍑哄畬鎴�
+        setTimeout(() => this.releaseSeatResource(), 1000);
+      }
+    },
     // 鏂紑WebSocket杩炴帴
     disconnectWebSocket() {
       if (this.ws) {
@@ -333,19 +558,11 @@
 
     // 绀哄繖
     afk() {
-      const { seatname, seatnum } = this.config;
-
-      if (
-        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
-      ) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "afk",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -353,19 +570,11 @@
 
     // 绀洪棽
     online() {
-      const { seatname, seatnum } = this.config;
-
-      if (
-        !this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
-      ) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "online",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -391,27 +600,28 @@
 
     // 鎸傛満
     hangup() {
-      const { seatname, seatnum } = this.config;
-
-      if (!this.validateParams({ seatnum }, ["seatnum"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "hangup",
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
     },
 
     // 澶栧懠
-    callout() {
-      const { seatname, seatnum, phone } = this.config;
+    // 澶栧懠鎿嶄綔
+    async callout(phoneNumber = null) {
+      const phone = phoneNumber || this.customerPhone || this.config.phone;
+      if (!phone) {
+        this.addLog("閿欒: 琚彨鍙风爜涓嶈兘涓虹┖");
+        this.$emit("error", { type: "phone_number_required" });
+        return;
+      }
 
-      if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
+      if (!this.isSeatLoggedIn) {
+        this.addLog("閿欒: 搴у腑鏈鍏ワ紝鏃犳硶澶栧懠");
         return;
       }
 
@@ -419,13 +629,23 @@
         cmd: "control",
         action: "callout",
         phone: phone,
-        seatname: seatname,
-        seatnum: seatnum,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
-      this.sendWebSocketMessage(protocol);
-    },
 
+      this.sendWebSocketMessage(protocol);
+      this.$emit("call-status", { status: "calling", phone });
+    },
+    // 娓呯悊璧勬簮
+    cleanup() {
+      this.stopHeartbeat();
+      if (this.ws) {
+        this.ws.close();
+        this.ws = null;
+      }
+      this.releaseSeatResource();
+    },
     // 閫氳瘽杞Щ
     transfer() {
       const { seatname, seatnum, phone, uuid } = this.config;
@@ -480,18 +700,12 @@
 
     // 閫氳瘽淇濇寔
     hold() {
-      const { seatname, seatnum, uuid } = this.config;
-
-      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "hold",
-        uuid: uuid,
-        seatname: seatname,
-        seatnum: seatnum,
+        uuid: this.config.uuid,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -499,18 +713,12 @@
 
     // 閫氳瘽淇濇寔鏀跺洖
     holdresume() {
-      const { seatname, seatnum, uuid } = this.config;
-
-      if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "control",
         action: "holdresume",
-        uuid: uuid,
-        seatname: seatname,
-        seatnum: seatnum,
+        uuid: this.config.uuid,
+        seatname: this.config.seatname,
+        seatnum: this.config.seatnum,
         timestamp: Date.now(),
       };
       this.sendWebSocketMessage(protocol);
@@ -1059,12 +1267,7 @@
       this.sendWebSocketMessage(protocol);
     },
 
-    // 蹇冭烦鍖�
     keepalive(seatname, seatnum) {
-      if (!this.validateParams({ seatnum }, ["seatnum"])) {
-        return;
-      }
-
       const protocol = {
         cmd: "system",
         action: "keepalive",
@@ -1097,6 +1300,87 @@
 </script>
 
 <style scoped>
+.status-indicator {
+  margin-bottom: 15px;
+  padding: 10px;
+  background: #f5f5f5;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.status-dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  display: inline-block;
+}
+
+.status-dot.connected {
+  background-color: #52c41a;
+}
+
+.status-dot.disconnected {
+  background-color: #f5222d;
+}
+
+.status-dot.logged-in {
+  background-color: #1890ff;
+}
+
+.status-dot.logged-out {
+  background-color: #d9d9d9;
+}
+
+.button-row button:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.config-area {
+  margin-bottom: 20px;
+}
+
+.input-group {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.input-group label {
+  font-weight: bold;
+  min-width: 80px;
+}
+
+.input-group input {
+  padding: 5px 10px;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  width: 120px;
+}
+
+.button-area {
+  margin-bottom: 20px;
+}
+
+.button-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 5px;
+  margin-bottom: 10px;
+}
+
+.log-area {
+  height: 200px;
+  overflow-y: auto;
+  border: 1px solid #ccc;
+  padding: 10px;
+  background: #f5f5f5;
+  white-space: pre-wrap;
+  font-family: monospace;
+}
 .websocket-demo {
   font-family: Arial, sans-serif;
   padding: 20px;

--
Gitblit v1.9.3