| | |
| | | <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" /> |
| | |
| | | |
| | | <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">代答</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">代接</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> |
| | | <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: { |
| | | // 初始化WebSocket连接 |
| | | initializeWebSocket() { |
| | | try { |
| | | // 根据当前页面协议自动选择WS协议 |
| | | const isHttps = window.location.protocol === "https:"; |
| | | this.config.cti_ws_url = isHttps |
| | | ? "wss://9.208.2.190:8092/cal-api/" |
| | |
| | | this.connectWebSocket(); |
| | | } catch (error) { |
| | | this.addLog(`初始化WebSocket错误: ${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://") |
| | |
| | | |
| | | 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) => { |
| | |
| | | |
| | | this.ws.onclose = (event) => { |
| | | this.isConnected = false; |
| | | this.isSeatLoggedIn = false; |
| | | this.stopHeartbeat(); |
| | | this.addLog(`WebSocket连接关闭: ${event.code} ${event.reason}`); |
| | | |
| | | // 自动重连 |
| | | 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) { |
| | | try { |
| | | // 检查数据类型:可能是字符串或Blob |
| | | if (event.data instanceof Blob) { |
| | | // 处理二进制数据(Blob) |
| | | const reader = new FileReader(); |
| | | reader.onloadend = (e) => { |
| | | const message = reader.result; |
| | | this.addLog(`收到消息: ${message}`); |
| | | 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}`); |
| | | } |
| | | }, |
| | | // 专门处理解析后的WebSocket数据 |
| | | processWebSocketData(messageText) { |
| | | console.log(messageText,'消息1'); |
| | | |
| | | try { |
| | | const obj = JSON.parse(message); |
| | | 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(); |
| | | } |
| | | |
| | | // 自动设置UUID |
| | | // 处理签入响应 |
| | | 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(`消息解析错误: ${error.message}`); |
| | | this.addLog(`JSON解析错误: ${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) { |
| | |
| | | |
| | | // 示忙 |
| | | 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); |
| | |
| | | |
| | | // 示闲 |
| | | 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); |
| | |
| | | |
| | | // 挂机 |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | |
| | | // 通话保持 |
| | | 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); |
| | |
| | | |
| | | // 通话保持收回 |
| | | 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); |
| | |
| | | this.sendWebSocketMessage(protocol); |
| | | }, |
| | | |
| | | // 心跳包 |
| | | keepalive(seatname, seatnum) { |
| | | if (!this.validateParams({ seatnum }, ["seatnum"])) { |
| | | return; |
| | | } |
| | | |
| | | const protocol = { |
| | | cmd: "system", |
| | | action: "keepalive", |
| | |
| | | </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; |