<template> <div class="ivr-div"> <ivr-btn :show="!btnStatus.btnLogin" :disabled="btnStatus.btnLogin" icon="qianru" @click="login">签入</ivr-btn> <ivr-btn :show="!btnStatus.btnLogOut" :disabled="btnStatus.btnLogOut" icon="qianchu" @click="logout">签出</ivr-btn> <ivr-btn :show="btnStatus.btnUHold" :disabled="btnStatus.btnHold" icon="baochi" @click="hold">保持</ivr-btn> <ivr-btn :show="!btnStatus.btnUnhold" :disabled="btnStatus.btnUnhold" icon="baochi" @click="unhold">解除保持</ivr-btn> <ivr-btn :show="true" :disabled="btnStatus.btnDialIn" icon="hujiao" @click="dialIn">内呼</ivr-btn> <ivr-btn :show="true" :disabled="btnStatus.btnDialOut" icon="duiwaihujiao" @click="dialOut">外呼</ivr-btn> <ivr-btn :show="btnStatus.btnHold" :disabled="btnStatus.btnHold" icon="baochi" @click="logout">保持</ivr-btn> <ivr-btn :disabled="btnStatus.btnHangUp" icon="guaji" @click="hangup">挂机</ivr-btn> <!--<el-button v-show="!btnStatus.btnLogin" :disabled="btnStatus.btnLogin" @click="login">--> <!--<svg-icon icon-class="qianru" class="ivr-icon"/>--> <!--<div>签入</div>--> <!--</el-button>--> <!--<el-button v-show="!btnStatus.btnLogOut" :disabled="btnStatus.btnLogOut" @click="logout">--> <!--<svg-icon icon-class="qianchu" class="ivr-icon"/>--> <!--<div>签出</div>--> <!--</el-button>--> <!--<el-button v-show="btnStatus.btnUnhold" :disabled="btnStatus.btnHold" @click="hold">--> <!--<svg-icon icon-class="baochi" class="ivr-icon"/>--> <!--<div>保持</div>--> <!--</el-button>--> <!--<el-button v-show="!btnStatus.btnUnhold" :disabled="btnStatus.btnUnhold" @click="unhold">--> <!--<svg-icon icon-class="baochi" class="ivr-icon"/>--> <!--<div>解除保持</div>--> <!--</el-button>--> <!--<el-button :disabled="btnStatus.btnDialIn" @click="dialIn">--> <!--<svg-icon icon-class="hujiao" class="ivr-icon"/>--> <!--<div>内呼</div>--> <!--</el-button>--> <!--<el-button :disabled="btnStatus.btnDialOut" @click="dialOut">--> <!--<svg-icon icon-class="duiwaihujiao" class="ivr-icon"/>--> <!--<div>外呼</div>--> <!--</el-button>--> <el-button :disabled="btnStatus.btnHangUp" @click="logout"> <svg-icon icon-class="hy" class="ivr-icon"/> <div>会议</div> </el-button> <el-button :disabled="btnStatus.btnIdle" @click="onidle"> <svg-icon icon-class="xiaoxiu" class="ivr-icon"/> <div>小休</div> </el-button> <el-button :disabled="btnStatus.btnBusy" @click="onbusy"> <svg-icon icon-class="shimang" class="ivr-icon"/> <div>示忙</div> </el-button> <el-button :disabled="btnStatus.btnLogin" type="text">{{ status }}</el-button> <div class="state-show"> <div>来电号码:<span class="highlight">{{ number }}</span></div> <div>通话时间:<span class="highlight">{{ timeFormat }}</span><span class="highlight">{{ call }}</span></div> </div> <div class="row clearfix"> <div id="div_state" class="alert alert-info"> <h3 class="text-center">状态:未连接</h3> </div> <div id="div_statusChanged"> <h4>状态改变:</h4> </div> <div id="div_callStatus"> <h4>通话状态:</h4> </div> <div id="div_queueStatus"> <h4>队列状态:</h4> </div> </div> <keyboard ref="keyboard" @call="dial"/> <!--外呼状态栏--> <el-dialog :title="statusText" :visible.sync="statusShow" width="30%" append-to-body> <span>{{ statusDetailText }}</span><span class="dot">...</span> <!--<span slot="footer" class="dialog-footer">--> <!--<el-button @click="dialogVisible = cancel">取 消</el-button>--> <!--</span>--> </el-dialog> <!--新建事件页面--> <el-dialog :visible.sync="showAddCase" :close-on-click-modal="false" :close-on-press-escape="false" title="新建事件" width="1200px" custom-class="addcase-dialog" top="5vh" append-to-body > <create-case ref="addcase"/> </el-dialog> </div> </template> <script> import sapoOcx from 'sapoOcx' import Keyboard from './keyboard' import IvrBtn from './ivrBtn' import CreateCase from '@/views/caseManage/createCase' import { getToday } from '@/utils/dateutils' export default { name: 'IvrBar', components: { CreateCase, IvrBtn, Keyboard }, data() { return { debug: true, // 是否开启debug模式 txtPhoneAccount: '', // 分机号 agentName: 'test', // 坐席名 queue: 'kefu', // 技能组 staffId: '10101', // 工号 skill: '1', // 技能值 busyString: '忙碌', // 致忙原因 deptid: '1', // 部门编号 deptName: '客服部', // 部门名称 autoAcw: '0', // 自动化后 domain: '', security: '', seatsList: [], // 坐席列表 hearbeartInterval: 0, webSocket: {}, webVariable: { web_version: '2.17.0505', web_agentStaffid: null, web_agentName: null, web_skill: null, web_reg: null, web_callStatue: null, web_busyStatue: null, web_number: null, web_heartbeatCount: 0, web_hearbeartInterval: 0, web_hearbeartID: 0 }, btnStatus: { btnLogin: true, // 签入 btnLogOut: true, // 签出 btnHold: true, // 保持 btnUnhold: true, // 解除保持 btnHangUp: true, // 挂机 btnDialOut: true, // 外呼 btnDialIn: true, // 内呼 btnAgentList: true, btnBusy: true, // 示忙 btnIdle: true, // 示闲 btnMeeting: true, // 会议 btnPickUp: true, btnExtenPickUp: true, btnGroupPickUp: true, btnSpy: true, btnGroupSpy: true, btnVersion: true, btnGetExtension: true, btnGetAgentStaffid: true, btnGetAgentName: true, btnGetSkill: true, btnGetBusyState: true, btnGetAgentState: true, btnGetCallState: true, btnGetRegState: true, btnGetNumber: true, btnInject: true }, status: '', busy: '', // 坐席忙碌状态 :忙碌,'', 另一坐席已登录 call: '', // 通话状态 isInCall: false, // 是否在通话中 reg: '', // 话机状态 number: '', // 通话号码 diaStatus: '', // 拨号状态 statusText: '', // 拨号状态标题 statusDetailText: '', // 拨号状态详情 statusShow: false, // 拨号的状态显示 time: 0, // 来电计时,单位:秒 timeInterval: null, threeWayPhone: '', // 三方通话电话号码 ip: '124.205.241.254', // websocket Ip port: '28081', // webSocket Port showAddCase: true // 是否显示新建案卷的弹窗 } }, computed: { timeFormat() { // 将时间格式化为时分秒样式 const h = Math.floor(this.time / 3600) const m = Math.floor((this.time - h * 3600) / 60) const s = this.time - h * 3600 - m * 60 const str = ('0' + h).slice(-2) + ':' + ('0' + m).slice(-2) + ':' + ('0' + s).slice(-2) return str } }, watch: { busy(val) { // 忙碌状态为不忙时,示忙按钮允许点,示闲不允许 if (val === '') { this.btnStatus.btnBusy = false this.btnStatus.btnIdle = true } else if (val === '忙碌') { this.btnStatus.btnBusy = true this.btnStatus.btnIdle = false } } }, created() { this.initSapoOcx() }, methods: { // 坐席签入 login() { // 如果没有分机号,先输入分机号,有分机号直接签入 if (!this.txtPhoneAccount) { this.$prompt('请输入分机号', '提示', { confirmButtonText: '确定', cancelButtonText: '取消' }).then(({ value }) => { this.txtPhoneAccount = value this.doLogin() }) } else { this.doLogin() } }, // websocket执行签入操作 doLogin() { // 替代sapoOcx.login if (this.hearbeartInterval == null || this.hearbeartInterval <= 0) { this.hearbeartInterval = 0 } this.webVariable.web_hearbeartInterval = this.hearbeartInterval this.webVariable.web_extension = this.txtPhoneAccount this.webVariable.web_agentStaffid = this.staffId this.webVariable.web_agentName = this.agentName this.webVariable.web_skill = this.skill // 替代socket.api.login const sendObj = { agent: this.agentName, extension: this.txtPhoneAccount, queue: this.queue, staffid: this.staffId, busyString: this.busyString, skill: this.skill, secid: this.deptid, secname: this.deptName, autoacw: this.autoAcw, domain: this.domain, security: this.security } const strSend = this.createCommand('login', sendObj) if (this.debug) { console.log(strSend) } this.webSocket.send(strSend) }, // 处理登录之后的操作 handleLoginResult(object) { if (object.success === 'true') { // 登录成功 // 处理其他按钮的可点击情况 this.btnStatus.btnLogin = true this.btnStatus.btnLogOut = false this.btnStatus.btnDialOut = false this.btnStatus.btnDialIn = false this.btnStatus.btnHold = false this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = false this.btnStatus.btnIdle = false this.btnStatus.btnIdle = false this.btnStatus.btnMeeting = false this.btnStatus.btnHangUp = false this.call = '空闲' // 心跳监测定时器 if (this.webVariable.web_hearbeartInterval > 0) { this.webVariable.web_hearbeartID = setInterval(this.sendHearbeatMessage(), this.webVariable.web_hearbeartID * 1000) } if (this.busy === '') { // OnFree_cb() } else { // OnBusy_cb(busy) // } } } else { // 登录失败,显示错误信息 this.showError(object.resultText) if (object.resultText === '座席登录失败, 分机号码错误') { this.txtPhoneAccount = '' } } }, // 坐席签出 logout() { const strSend = 'command:logout\n\n' this.webSocket.send(strSend) }, // 处理签出成功之后的操作 handleLogoutResult(object) { if (object.success) { this.$message.success('签出成功') // 处理按钮状态 this.btnStatus.btnLogin = false this.btnStatus.btnLogOut = true this.btnStatus.btnDialOut = true this.btnStatus.btnDialIn = true this.btnStatus.btnHold = true this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = true this.btnStatus.btnIdle = true this.btnStatus.btnIdle = true this.btnStatus.btnMeeting = true this.btnStatus.btnHangUp = true this.resetWs() // 清除定时器 clearInterval(this.webVariable.web_hearbeartID) } }, // 外呼 dialOut() { this.$refs.keyboard.initDialog('out') }, // 内呼 dialIn() { this.$refs.keyboard.initDialog('in') }, // 拨号 dial(type, tel) { if (type === 'out') { tel = '9' + tel } const sendObj = { dialString: tel, cusid: '', cusname: '', proid: '', proname: '' } this.statusShow = true this.statusText = '呼叫中...' this.statusDetailText = '呼叫中' const strSend = this.createCommand('dial', sendObj) this.webSocket.send(strSend) }, // 示忙 onbusy() { const strSend = this.createCommand('busy') this.webSocket.send(strSend) }, // 示闲 onidle() { const strSend = this.createCommand('idle') this.webSocket.send(strSend) }, // 挂机 hangup() { const strSend = this.createCommand('hangup') this.webSocket.send(strSend) }, // 保持 hold() { const strSend = this.createCommand('hold') this.webSocket.send(strSend) }, // 解除保持 unhold() { const strSend = this.createCommand('unhold') this.webSocket.send(strSend) }, // 三方通话 threeWay() { const sendObj = { dialString: this.threeWayPhone } const strSend = this.createCommand('threeWay', sendObj) this.webSocket.send(strSend) }, // 初始化 initSapoOcx() { const { ip, port } = this sapoOcx.api.setDebugMode() // 连接服务器,参数:ip,端口,初始化websocket this.connect(ip, port) }, // 创建连接 connect: function(server, port) { sapoOcx.api.config.serverIp = server if (port != null && port !== '') { sapoOcx.api.config.commuPort = port } this.connectToServer() }, // 连接服务器 connectToServer: function() { const that = this if (!sapoOcx.api.config.initFinish) { sapoOcx.api.config.initFinish = true setTimeout(that.connectToServer(), 200) } else { this.initConnection(sapoOcx.api.config.serverIp, sapoOcx.api.config.commuPort) } }, // 初始化websocket连接 initConnection(ip, port) { const wsUrl = 'ws://' + ip + ':' + port + '/spcc/cti' this.webSocket = new WebSocket(wsUrl) this.webSocket.onopen = this.wsOpen this.webSocket.onclose = this.wsClose this.webSocket.onmessage = this.wsMessage this.webSocket.onerror = this.wsError }, // webSocket onOpen wsOpen(event) { this.status = '已连接' this.btnStatus.btnLogin = false }, // webSocket onClose wsClose(event) { this.resetWs() clearInterval(this.webVariable.web_hearbeartID) console.log('quit success!') }, // 处理websocket 返回消息 wsMessage(event) { const that = this // 将websocket 返回值转成对象 object const obj = event.data const data = obj.split('\n') const object = {} data.forEach(function(value) { if (value !== '') { const objData = value.split(':') object[objData[0]] = objData[1] } }) console.log(object) // 根据返回值处理 const messageType = object.messageType // 消息类型 if (messageType === 'event') { const name = object.name if (name === 'agent status') { // 坐席状态 } if (name === 'call status') { // 话机状态 if (object.call === '空闲') { this.call = '空闲' // 如果正在通话 if (this.isInCall === true) { this.statusDetailText = '通话结束' this.statusShow = false // 结束计时器 clearInterval(this.timeInterval) this.timeInterval = null this.time = 0 // TODO:向后台添加通话记录 } } else if (object.call === '外呼通话') { this.call = '通话中' this.isInCall = true this.statusDetailText = object.call // 开始通话后3s弹窗消失 setTimeout(function() { that.statusShow = false }, 3000) this.time = 0 this.timeInterval = setInterval(function() { that.time += 1 }, 1000) } else { this.isInCall = true this.statusDetailText = object.call } } if (name === 'outbound ringing') { // 外呼振铃,除了挂断其他不允许点 this.btnStatus.btnDialOut = true this.btnStatus.btnDialIn = true this.btnStatus.btnHold = true this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = true this.btnStatus.btnIdle = true this.btnStatus.btnMeeting = true this.btnStatus.btnHangUp = false } if (name === 'outbound answered') { // 外呼接通,允许保持 this.btnStatus.btnDialOut = true this.btnStatus.btnDialIn = true this.btnStatus.btnHold = false this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = true this.btnStatus.btnIdle = true this.btnStatus.btnMeeting = true this.btnStatus.btnHangUp = false } if (name === 'incomming ringing') { this.$notify({ title: '未接来电', message: object.number, duration: 0 }) } if (name === 'incomming answered') { // 来电接通 this.btnStatus.btnDialOut = true this.btnStatus.btnDialIn = true this.btnStatus.btnHold = false this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = true this.btnStatus.btnIdle = true this.btnStatus.btnMeeting = true this.btnStatus.btnHangUp = false // 弹出新建工单窗口 this.showAddCase = true const data = { callid: object.callid, number: object.number, callTime: getToday('yyyy-MM-dd hh:mm:ss') } console.log(data) this.$refs['addcase'].initData(data) } if (name === 'hangup') { // 挂断, 冻结和解除冻结不好用,其他均可 this.btnStatus.btnDialOut = false this.btnStatus.btnDialIn = false this.btnStatus.btnHold = true this.btnStatus.btnUnhold = true this.btnStatus.btnBusy = false this.btnStatus.btnIdle = false this.btnStatus.btnMeeting = false this.btnStatus.btnHangUp = false } if (name === 'status changed') { // 状态改变 this.busy = object.busy // 坐席忙碌状态 // this.call = object.call // 通话忙碌状态 this.reg = object.reg // 话机状态 this.number = object.number ? object.number : '' // 来电号码 this.webVariable.web_reg = object.reg this.webVariable.web_callStatue = object.call this.webVariable.web_agentStatue = object.agent this.webVariable.web_busyStatue = object.busy this.webVariable.web_number = object.number if (this.reg === '离线') { this.$message.warning('话机已离线,请检查!') if (this.statusShow === true) { // 如果有呼叫弹窗,关闭 this.statusShow = false } } else { // 话机状态 this.status = '话机已连接' } } if (name === 'queue status') { // OnQueueStatusChanged_cb(object.idle, object.agent, object.guest, object.queue) } if (name === 'busy status') { // busy = object.busy // if (busy == '') { // OnFree_cb() // } else { // OnBusy_cb(busy) // } } } else if (messageType === 'heartbeat response') { // 心跳回复 this.webVariable.web_hearbeatCount = 0 } else if (messageType === 'command response') { // 命令回复 const command = object.command if (command === 'login') { this.handleLoginResult(object) } if (command === 'logout') { this.handleLogoutResult(object) } if (command === 'getExtensionList') { this.handleExtensionList(object) } if (command === 'dial') { if (object.success === 'true') { this.statusDetailText = object.resultText } } if (object.resultText === '准备挂断' && object.success) { this.statusShow = false } } }, // 获取坐席列表,存到seatsList handleExtensionList(object) { if (object.success) { const test = object.resultText const index = test.indexOf(',') if (index > 0) { const messageList = test.split(',') console.log(messageList.length) for (let i = 0; i < messageList.length; i++) { const tList = messageList[i].split('|') const seat = { exten: tList[0], name: tList[1], loginId: tList[2], sector: tList[3], group: tList[4], state: tList[5], busy: tList[6] } this.seatsList.push(seat) } } } }, // webSocket onError wsError(event) { console.log(event) }, resetWs() { this.webVariable.web_extension = null this.webVariable.web_agentStaffid = null this.webVariable.web_agentName = null this.webVariable.web_skill = null this.webVariable.web_reg = null this.webVariable.web_callStatue = null this.webVariable.web_agentStatue = null this.webVariable.web_busyStatue = null this.webVariable.web_number = null }, // 发送心跳,三次没有响应则断开连接 sendHearbeatMessage() { if (this.webVariable.web_hearbeatCount >= 3) { this.webSocket.close() } else { this.webVariable.web_hearbeatCount += 1 const strSend = this.createCommand('heartbeat') this.webSocket.send(strSend) } }, // 显示错误信息 showError(message) { this.$message.error(message) }, // 创建命令 createCommand(command, object = null) { let sendStr = 'command:' if (command) { sendStr += command + '\n' if (object) { for (const key in object) { sendStr += key + ':' + object[key] + '\n' } } sendStr += '\n' } return sendStr } } } </script> <style rel="stylesheet/scss" lang="scss" scoped> .ivr-div{ position: fixed; top:0px; left:240px; padding-top: 10px; .el-button--default{ width:50px; display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background-color:transparent; border-style:none; color: #ffffff; -webkit-appearance: none; text-align: center; -webkit-box-sizing: border-box; box-sizing: border-box; outline: none; margin: 0; -webkit-transition: .1s; transition: .1s; font-weight: 500; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; padding: 3px 3px; font-size: 12px; .ivr-icon{ width: 100%; text-align: center; display: block; font-size: 25px; margin-bottom: 2px; } } .el-button.is-disabled, .el-button.is-disabled:hover, .el-button.is-disabled:focus{ color: #e1e1e1; .ivr-icon{ color: #e1e1e1 !important; } } .state-show{ display: inline-block; width:180px; border:1px solid #409EFF; color:#ffffff; font-size:12px; line-height: 17px; padding:5px 5px; height: 45px; box-sizing: border-box; transform: translate(0, -8px); } .highlight{ font-size:13px; font-weight: 600; color:#00ffff; padding-left: 5px; } } </style> <style> .addcase-dialog{ } .dot { font-family: simsun; } :root .dot { display: inline-block; width: 1.5em; vertical-align: bottom; overflow: hidden; } @-webkit-keyframes dot { 0% { width: 0; margin-right: 1.5em; } 33% { width: .5em; margin-right: 1em; } 66% { width: 1em; margin-right: .5em; } 100% { width: 1.5em; margin-right: 0;} } .dot { -webkit-animation: dot 3s infinite step-start; } @keyframes dot { 0% { width: 0; margin-right: 1.5em; } 33% { width: .5em; margin-right: 1em; } 66% { width: 1em; margin-right: .5em; } 100% { width: 1.5em; margin-right: 0;} } .dot { animation: dot 3s infinite step-start; } </style>