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