<template>
|
<div class="websocket-demo">
|
<div>
|
<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="wss://your-server.com"
|
/>
|
|
<label>坐席工号</label>
|
<input
|
type="text"
|
v-model="config.seatname"
|
:placeholder="randomNum"
|
/>
|
|
<label>坐席分机</label>
|
<input
|
type="text"
|
v-model="config.seatnum"
|
:placeholder="randomNum"
|
/>
|
|
<label>密码</label>
|
<input type="text" v-model="config.password" placeholder="123456" />
|
</div>
|
|
<div class="input-group">
|
<label>外线号码</label>
|
<input
|
type="text"
|
v-model="customerPhone"
|
placeholder="请输入电话号码"
|
/>
|
|
<label>技能组</label>
|
<input type="text" v-model="config.group" placeholder="a3" />
|
</div>
|
</div>
|
|
<!-- 操作按钮区域 -->
|
<div class="button-area">
|
<div class="button-row">
|
<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="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 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: "",
|
seatname: "",
|
seatnum: "",
|
password: "123456",
|
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,
|
};
|
},
|
|
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.cleanup();
|
},
|
|
methods: {
|
// 初始化WebSocket连接
|
initializeWebSocket() {
|
try {
|
const isHttps = window.location.protocol === "https:";
|
this.config.cti_ws_url = isHttps
|
? "wss://9.208.2.190:8092/cal-api/"
|
: "ws://40.78.0.169:6688";
|
|
if (typeof window.WebSocket === "undefined") {
|
this.addLog("错误: 浏览器不支持WebSocket");
|
return;
|
}
|
|
this.connectWebSocket();
|
} catch (error) {
|
this.addLog(`初始化WebSocket错误: ${error.message}`);
|
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) {
|
return;
|
}
|
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
this.addLog("错误: 达到最大重连次数,停止重连");
|
return;
|
}
|
|
try {
|
let wsUrl = this.config.cti_ws_url;
|
if (
|
window.location.protocol === "https:" &&
|
wsUrl.startsWith("ws://")
|
) {
|
wsUrl = wsUrl.replace("ws://", "wss://");
|
}
|
|
this.ws = new WebSocket(wsUrl);
|
|
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.handleWebSocketMessage(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}`);
|
};
|
} catch (error) {
|
this.addLog(`连接WebSocket失败: ${error.message}`);
|
setTimeout(() => this.connectWebSocket(), 3000);
|
}
|
},
|
|
// 处理WebSocket消息
|
handleWebSocketMessage(event) {
|
try {
|
// 检查数据类型:可能是字符串或Blob
|
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}`);
|
}
|
},
|
// 专门处理解析后的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解析错误: ${error.message}, 原始数据: ${messageText}`);
|
}
|
},
|
// 处理呼叫状态变化
|
handleCallStatusChange(obj) {
|
const statusMap = {
|
ringing: "振铃中",
|
connected: "通话中",
|
held: "已保持",
|
ended: "通话结束",
|
};
|
|
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) {
|
this.ws.close();
|
this.ws = null;
|
this.isConnected = false;
|
this.addLog("WebSocket已断开");
|
}
|
},
|
|
// 发送WebSocket消息
|
sendWebSocketMessage(message) {
|
if (!this.isConnected || !this.ws) {
|
this.addLog("错误: WebSocket未连接");
|
return false;
|
}
|
|
try {
|
const messageStr =
|
typeof message === "string" ? message : JSON.stringify(message);
|
this.ws.send(messageStr);
|
this.addLog(`发送消息: ${messageStr}`);
|
return true;
|
} catch (error) {
|
this.addLog(`发送消息失败: ${error.message}`);
|
return false;
|
}
|
},
|
|
// 验证参数
|
validateParams(params, requiredFields) {
|
for (const field of requiredFields) {
|
if (!params[field] || params[field].toString().trim() === "") {
|
this.addLog(`错误: ${field} 不能为空`);
|
return false;
|
}
|
}
|
return true;
|
},
|
|
// ==================== WebSocket.js 功能整合 ====================
|
|
// 签入
|
seatlogin() {
|
const { seatname, seatnum, password, cti_ws_url } = this.config;
|
|
if (
|
!this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
|
) {
|
return;
|
}
|
|
// 重新连接WebSocket(原js文件中的逻辑)
|
this.connectWebSocket();
|
setTimeout(() => {
|
const protocol = {
|
cmd: "system",
|
action: "seatlogin",
|
seatname: seatname,
|
seatnum: seatnum,
|
password: password,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
}, 1000);
|
},
|
|
// 签出
|
seatlogout() {
|
const { seatname, seatnum } = this.config;
|
|
if (
|
!this.validateParams({ seatname, seatnum }, ["seatname", "seatnum"])
|
) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "system",
|
action: "seatlogout",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
this.ws.close();
|
},
|
|
// 示忙
|
afk() {
|
const protocol = {
|
cmd: "system",
|
action: "afk",
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 示闲
|
online() {
|
const protocol = {
|
cmd: "system",
|
action: "online",
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 代答
|
pickup() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "pickup",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 挂机
|
hangup() {
|
const protocol = {
|
cmd: "control",
|
action: "hangup",
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 外呼
|
// 外呼操作
|
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.isSeatLoggedIn) {
|
this.addLog("错误: 座席未签入,无法外呼");
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "callout",
|
phone: phone,
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
|
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;
|
|
if (
|
!this.validateParams({ seatnum, phone, uuid }, [
|
"seatnum",
|
"phone",
|
"uuid",
|
])
|
) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "transfer",
|
uuid: uuid,
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 通话转移收回
|
transferresume() {
|
const { seatname, seatnum, phone, uuid } = this.config;
|
|
if (
|
!this.validateParams({ seatnum, phone, uuid }, [
|
"seatnum",
|
"phone",
|
"uuid",
|
])
|
) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "transferresume",
|
uuid: uuid,
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 通话保持
|
hold() {
|
const protocol = {
|
cmd: "control",
|
action: "hold",
|
uuid: this.config.uuid,
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 通话保持收回
|
holdresume() {
|
const protocol = {
|
cmd: "control",
|
action: "holdresume",
|
uuid: this.config.uuid,
|
seatname: this.config.seatname,
|
seatnum: this.config.seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 通话强拆
|
remove() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "remove",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 通话强插
|
insert() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "insert",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 监听
|
monitor() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "monitor",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 监听转通话
|
monitor_to_talk() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "monitor_to_talk",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 监听结束
|
monitor_end() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "monitor_end",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 选择通话
|
choosecall() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "choosecall",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 代接
|
replacecall() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "replacecall",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 三方通话
|
three() {
|
const { seatname, seatnum, phone } = this.config;
|
|
if (!this.validateParams({ seatnum, phone }, ["seatnum", "phone"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "three",
|
phone: phone,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 咨询开始
|
handoff_ready() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "handoff_ready",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 咨询呼叫
|
handoff_call() {
|
const { seatname, seatnum, other, uuid } = this.config;
|
|
if (
|
!this.validateParams({ seatnum, other, uuid }, [
|
"seatnum",
|
"other",
|
"uuid",
|
])
|
) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "handoff_call",
|
uuid: uuid,
|
phone: other,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 咨询收回
|
handoff_resume() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "handoff_resume",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 咨询转移
|
handoff_transfer() {
|
const { seatname, seatnum, other, uuid } = this.config;
|
|
if (
|
!this.validateParams({ seatnum, other, uuid }, [
|
"seatnum",
|
"other",
|
"uuid",
|
])
|
) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "handoff_transfer",
|
uuid: uuid,
|
phone: other,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 咨询三方
|
handoff_three() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "handoff_three",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 开始通话录音
|
record_start() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "record_start",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 停止通话录音
|
record_stop() {
|
const { seatname, seatnum, uuid } = this.config;
|
|
if (!this.validateParams({ seatnum, uuid }, ["seatnum", "uuid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "control",
|
action: "record_stop",
|
uuid: uuid,
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 打开坐席状态
|
openseatlist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "openseatlist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 关闭坐席状态
|
closeseatlist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "closeseatlist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 打开队列信息
|
openqueues() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "openqueues",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 关闭队列信息
|
closequeues() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "closequeues",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 打开通话信息
|
opencalllist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "opencalllist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 关闭通话信息
|
closecalllist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "closecalllist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 打开路由信息
|
openroutelist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "openroutelist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 关闭路由信息
|
closeroutelist() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "closeroutelist",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 获取坐席信息
|
seatlist() {
|
const { group } = this.config;
|
|
if (!this.validateParams({ group }, ["group"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "seatlist",
|
group: group,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 获取队列信息
|
queues() {
|
const protocol = {
|
cmd: "status",
|
action: "queues",
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 获取通话信息
|
calllist() {
|
const protocol = {
|
cmd: "status",
|
action: "calllist",
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 获取路由信息
|
routelist() {
|
const protocol = {
|
cmd: "status",
|
action: "routelist",
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 获取外呼参数信息
|
batch() {
|
const { paramid } = this.config;
|
|
if (!this.validateParams({ paramid }, ["paramid"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "status",
|
action: "batch",
|
paramid: paramid,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 开始外呼任务
|
batch_start() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "system",
|
action: "batch_start",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 停止外呼任务
|
batch_stop() {
|
const { seatname, seatnum } = this.config;
|
|
if (!this.validateParams({ seatnum }, ["seatnum"])) {
|
return;
|
}
|
|
const protocol = {
|
cmd: "system",
|
action: "batch_stop",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
keepalive(seatname, seatnum) {
|
const protocol = {
|
cmd: "system",
|
action: "keepalive",
|
seatname: seatname,
|
seatnum: seatnum,
|
timestamp: Date.now(),
|
};
|
this.sendWebSocketMessage(protocol);
|
},
|
|
// 清除日志
|
testclear() {
|
this.logs = "";
|
this.addLog("日志已清除");
|
},
|
|
// 添加日志
|
addLog(message) {
|
const timestamp = new Date().toLocaleTimeString();
|
this.logs += `[${timestamp}] ${message}\n`;
|
|
// 限制日志长度,防止内存溢出
|
const logLines = this.logs.split("\n");
|
if (logLines.length > 100) {
|
this.logs = logLines.slice(-50).join("\n");
|
}
|
},
|
},
|
};
|
</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;
|
max-width: 1200px;
|
margin: 0 auto;
|
}
|
|
.config-area {
|
margin-bottom: 20px;
|
padding: 15px;
|
border: 1px solid #ddd;
|
border-radius: 4px;
|
background-color: #f9f9f9;
|
}
|
|
.input-group {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
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;
|
}
|
|
.button-row button {
|
padding: 8px 15px;
|
border: 1px solid #ccc;
|
border-radius: 3px;
|
background-color: #f0f0f0;
|
cursor: pointer;
|
transition: background-color 0.3s;
|
font-size: 12px;
|
}
|
|
.button-row button:hover {
|
background-color: #e0e0e0;
|
}
|
|
.button-row button:active {
|
background-color: #d0d0d0;
|
transform: translateY(1px);
|
}
|
|
.log-area {
|
height: 300px;
|
overflow-y: auto;
|
border: 1px solid #ccc;
|
padding: 10px;
|
background-color: #f5f5f5;
|
white-space: pre-wrap;
|
font-family: "Courier New", monospace;
|
font-size: 12px;
|
line-height: 1.4;
|
}
|
|
h3 {
|
color: #333;
|
border-bottom: 2px solid #eee;
|
padding-bottom: 10px;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
h3 button {
|
padding: 5px 10px;
|
font-size: 12px;
|
background-color: #f0f0f0;
|
border: 1px solid #ccc;
|
border-radius: 3px;
|
cursor: pointer;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.websocket-demo {
|
padding: 10px;
|
}
|
|
.input-group {
|
flex-direction: column;
|
align-items: flex-start;
|
}
|
|
.input-group input {
|
width: 100%;
|
}
|
|
.button-row {
|
flex-direction: column;
|
}
|
|
.button-row button {
|
width: 100%;
|
margin-bottom: 5px;
|
}
|
}
|
</style>
|