Newer
Older
CallCenterFront / src / layout / components / ivr / ivrBar.vue
StephanieGitHub on 8 Apr 2020 23 KB MOD:坐席IVR构建
<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>