Browse Source

reactor:对接兴唐呼叫中心坐席监控;

zhangchong 6 months ago
parent
commit
b6a6fc64ce

+ 0 - 97
src/views/seatMonitor/container.vue

@@ -1,97 +0,0 @@
-<template>
-  <div class="index-box">
-    <div class="center_left">
-      <border-box class="center_left-left center_lr-item" title="24小时话务量">
-        <LeftTop :content="state.data1" />
-      </border-box>
-      <border-box
-        class="center_left-center center_lr-item-center"
-        title="24小时坐席话务TOP10"
-        style="margin-top: 10px"
-      >
-        <LeftCenter :content="state.data2" />
-      </border-box>
-      <border-box
-        class="center_left-bottom center_lr-item"
-        title="数据展示"
-        style="margin-top: 10px"
-      >
-        <LeftBottom :data="props.data" :content="state.data3" />
-      </border-box>
-    </div>
-    <div class="center_right">
-      <Right :data="props.data" />
-    </div>
-  </div>
-</template>
-<script setup lang="ts">
-import { onMounted, reactive } from "vue";
-import { agentPaged } from "api/seats";
-import BorderBox from "@/views/seats/border-box.vue";
-import LeftTop from "@/views/seatMonitor/left-top.vue";
-import LeftCenter from "@/views/seatMonitor/left-center.vue";
-import LeftBottom from "@/views/seatMonitor/left-bottom.vue";
-import Right from "@/views/seatMonitor/right.vue";
-
-const props = defineProps({
-  data: {
-    type: Array,
-    default: () => [],
-  },
-});
-const state = reactive<any>({
-  data1: [],
-  data2: [],
-  data3: {},
-});
-onMounted(async () => {
-  try {
-    const { result } = await agentPaged();
-    state.data1 = result[0] ?? [];
-    state.data2 = result[1] ?? [];
-    state.data3 = {
-      count: result[2] ?? [],
-      list: result[3] ?? [],
-    };
-  } catch (e) {
-    console.log(e);
-  }
-});
-</script>
-<style scoped lang="scss">
-.index-box {
-  width: 100%;
-  display: flex;
-  height: calc(100% - 150px);
-  font-size: var(--el-font-size-base);
-  color: var(--el-color-white);
-}
-
-.center_left {
-  display: flex;
-  flex-direction: column;
-  justify-content: space-around;
-  position: relative;
-  box-sizing: border-box;
-  flex-shrink: 0;
-  width: 570px;
-  background-color: rgba(0, 0, 0, 0.18);
-  padding: 0 16px;
-  margin-left: 10px;
-  border-radius: var(--el-border-radius-base);
-}
-
-.center_right {
-  box-sizing: border-box;
-  flex: 1;
-  margin-left: 20px;
-}
-
-.center_lr-item {
-  height: 242px;
-}
-
-.center_lr-item-center {
-  height: 360px;
-}
-</style>

+ 0 - 461
src/views/seatMonitor/header.vue

@@ -1,461 +0,0 @@
-<template>
-  <div class="title_wrap">
-    <!--    <el-button
-      round
-      class="title_wrap_btn"
-      @click="signIn"
-      v-if="!globalState.callCenterIsSignIn"
-      :icon="Phone"
-      >签入
-    </el-button>
-    <el-button
-      round
-      class="title_wrap_btn"
-      @click="signOut"
-      v-else
-      :icon="PhoneFilled"
-      >签出</el-button
-    >-->
-    <div class="title">
-      <span class="title-text">{{ title }}</span>
-    </div>
-    <div class="timers">
-      <span class="timers-text">
-        {{ formatDate(now, "YYYY年mm月dd日 HH:MM:SS WWW") }}</span
-      >
-    </div>
-    <div class="guang"></div>
-    <div class="left_icons">
-      <el-badge :value="readyCount" :max="99" class="left_icons_item">
-        <img src="@/assets/img/seats/ready.png" alt="" />空闲
-      </el-badge>
-      <el-badge :value="restCount" :max="99" class="left_icons_item">
-        <img src="@/assets/img/seats/unready.png" alt="" />小休
-      </el-badge>
-      <el-badge :value="meetingCount" :max="99" class="left_icons_item">
-        <img src="@/assets/img/seats/threeWay.png" alt="" />三方会议
-      </el-badge>
-      <el-badge :value="logoutCount" :max="99" class="left_icons_item">
-        <img src="@/assets/img/seats/logout.png" alt="" />签出
-      </el-badge>
-      <el-badge :value="callInCount" :max="99" class="left_icons_item">
-        <img src="@/assets/img/seats/callIn.png" alt="" />呼入
-      </el-badge>
-    </div>
-    <div class="right_icons">
-      <el-badge :value="callOutCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/callOut.png" alt="" />呼出
-      </el-badge>
-      <el-badge :value="consultCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/ring.png" alt="" />咨询
-      </el-badge>
-      <el-badge :value="otherCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/other.png" alt="" />其他
-      </el-badge>
-      <el-badge :value="callCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/busy.png" alt="" />通话
-      </el-badge>
-      <el-badge :value="cawCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/acw.png" alt="" />整理
-      </el-badge>
-      <el-badge :value="logoffCount" :max="99" class="right_icons_item">
-        <img src="@/assets/img/seats/logoff.png" alt="" />注销
-      </el-badge>
-    </div>
-    <!-- 签入弹窗 -->
-    <el-dialog
-      v-model="state.dutyDialogVisible"
-      draggable
-      title="签入"
-      width="500px"
-      :show-close="false"
-      append-to-body
-    >
-      <el-form :model="state.dutyForm" label-width="80px" ref="dutyFormRef">
-        <el-form-item
-          label="分机"
-          prop="currentTel"
-          :rules="[
-            {
-              required: true,
-              message: '请选择需要签入的分机',
-              trigger: 'change',
-            },
-          ]"
-        >
-          <el-select-v2
-            v-model="state.dutyForm.currentTel"
-            :options="state.telsList"
-            placeholder="选择要签入的分机"
-            filterable
-            class="w-full"
-            :height="500"
-            :props="{
-              label: 'telNo',
-              value: 'telNo',
-            }"
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button
-            @click="state.dutyDialogVisible = false"
-            :loading="state.loading"
-            >取 消</el-button
-          >
-          <el-button @click="clickOnDuty(dutyFormRef)" :loading="state.loading"
-            >确 定</el-button
-          >
-        </span>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-<script setup lang="ts">
-import { computed, onMounted, onUnmounted, reactive, ref, watch } from "vue";
-import signalR from "@/utils/signalR";
-import { useNow, useTitle } from "@vueuse/core";
-import { formatDate } from "@/utils/formatTime";
-import { olaFn } from "@/utils/olaFn";
-import { getNowDateTime } from "@/utils/constants";
-import { callCenterLogout, useGlobalState } from "@/utils/callCenter";
-import { ElMessageBox, FormInstance } from "element-plus";
-import { Phone, PhoneFilled } from "@element-plus/icons-vue";
-import { useThemeConfig } from "@/stores/themeConfig";
-import { storeToRefs } from "pinia";
-import { getListenExtension } from "api/seats";
-import mittBus from "@/utils/mitt";
-
-const storesThemeConfig = useThemeConfig();
-const { themeConfig } = storeToRefs(storesThemeConfig);
-const props = defineProps({
-  data: {
-    type: Array,
-    default: () => [],
-  },
-});
-const now = useNow(); // 获取当前时间
-const state = reactive({
-  dutyDialogVisible: false,
-  dutyForm: {
-    currentTel: null,
-  },
-  loading: false,
-  telsList: [],
-});
-// 签入
-const signIn = () => {
-  if (!globalState.callCenterIsSignIn) {
-    state.dutyDialogVisible = true;
-  } else {
-    globalState.callCenterIsSignIn = false;
-  }
-};
-const dutyFormRef = ref();
-// 确认签入
-const clickOnDuty = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.validate((valid: boolean) => {
-    if (!valid) return;
-    const currentPhone: any = state.telsList.find(
-      (item: any) => item.telNo === state.dutyForm.currentTel
-    );
-    globalState.currentTel = {
-      telNo: currentPhone.telNo,
-      telGroup: currentPhone.queue,
-      jobNum: currentPhone.TelNo,
-    };
-    websocket_connect(currentPhone.telNo, currentPhone.telPwd);
-    state.dutyDialogVisible = false;
-  });
-};
-// 签出
-const signOut = () => {
-  ElMessageBox.confirm(`签出后无法监听和插话,确定签出?`, "提示", {
-    confirmButtonText: "确认",
-    cancelButtonText: "取消",
-    type: "warning",
-    draggable: true,
-    cancelButtonClass: "default-button",
-    autofocus: false,
-  })
-    .then(async () => {
-      callCenterLogout();
-    })
-    .catch(() => {});
-};
-const globalState = useGlobalState();
-const olaRef = ref();
-const websocket_connect = (telNo: string, telPwd: string) => {
-  olaRef.value = olaFn(themeConfig.value.callCenterSocketUrl, {
-    username: telNo,
-    password: telPwd,
-    onConnected: onConnected, // 连接成功
-    onDisconnected: onDisconnected, // 断开链接
-    onMessage: onMessage, // 接收消息
-    onError: onError, // 错误
-    autoReconnect: {
-      delay: 2000,
-      // retries: 10,
-    }, // 自动重连
-    heartbeat: {
-      message: JSON.stringify({ cmd: "ping" }),
-      interval: 5000,
-      // pongTimeout: 1000,
-    },
-  });
-  console.log(`${getNowDateTime()}:开始链接呼叫中心`);
-};
-// 呼叫中心链接成功
-const onConnected = () => {
-  olaRef.value.logout(globalState.currentTel.telNo); //连接之后,先登出一次,防止其他地方已经登陆
-  let array_ola_queue = []; // 队列
-  // 普通模式
-  let array = globalState.currentTel.telGroup.split(",");
-  for (let i = 0; i < array.length; i++) {
-    array_ola_queue[i] = array[i];
-  }
-  olaRef.value.login(array_ola_queue, globalState.currentTel.telNo, {
-    type: "onhook",
-  });
-  globalState.callCenterWs = olaRef.value;
-  console.log(`${getNowDateTime()}:呼叫中心链接成功`);
-};
-// 呼叫中心链接关闭
-const onDisconnected = (event: any) => {
-  globalState.callCenterWs = null;
-  console.log(`${getNowDateTime()}:呼叫中心断开链接`, event);
-};
-// 呼叫中心链接错误
-const onError = (ws: any, e: any) => {
-  globalState.callCenterIsSignIn = false; // 签出状态
-  console.log(`${getNowDateTime()}:呼叫中心链接错误`, e);
-};
-// 呼叫中心消息
-const onMessage = async (event: any) => {
-  const data = JSON.parse(event);
-  // console.log(`${getNowDateTime()}:接收呼叫中心消息`, event);
-  if (data.event_type == "agent_state") {
-    switch (data.state) {
-      case "login": // 签入消息回调
-        globalState.callCenterIsSignIn = true; // 签入状态
-        setTimeout(() => {
-          // 设置示闲状态
-          olaRef.value.go_ready();
-        }, 300);
-        console.log(
-          `${getNowDateTime()}:接收消息:呼叫中心:已签入,当前分机:${
-            globalState.currentTel.telNo
-          }`
-        );
-        break;
-      case "logout": // 签出消息回调
-        globalState.callCenterIsSignIn = false; // 签出状态
-        console.log(`${getNowDateTime()}:接收消息:呼叫中心:已签出`);
-        break;
-      case "acw": // 话后整理回到
-        // 调用示闲
-        olaRef.value.go_ready(); // 示闲
-        console.log(`${getNowDateTime()}:接收消息:呼叫中心:已示闲`);
-        break;
-      case "unready": // 示忙消息回调
-        // 调用示闲
-        olaRef.value.go_ready(); // 示闲
-        console.log(`${getNowDateTime()}:接收消息:呼叫中心:已示闲`);
-        break;
-    }
-  }
-};
-// 空闲数量
-const readyCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "0").length;
-});
-// 小休数量、
-const restCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "1").length;
-});
-// 三方会议数量
-const meetingCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "2").length;
-});
-// 签出数量
-const logoutCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "3").length;
-});
-// 呼入数量
-const callInCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "4").length;
-});
-// 呼出数量
-const callOutCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "5").length;
-});
-// 咨询数量
-const consultCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "6").length;
-});
-// 其他数量
-const otherCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "7").length;
-});
-// 通话数量
-const callCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "8").length;
-});
-// 通话数量
-const cawCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "9").length;
-});
-// 注销数量
-const logoffCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.State === "10").length;
-});
-const seatsList = ref<any>([]);
-watch(
-  () => props.data,
-  (newData: any) => {
-    seatsList.value = newData;
-  },
-  { immediate: true }
-);
-// 获取可以监听的分机列表
-const getSeatsList = async () => {
-  try {
-    const { result } = await getListenExtension();
-    state.telsList = result;
-  } catch (e) {
-    console.log(e);
-  }
-};
-const title = ref("12345坐席监控中心");
-onMounted(() => {
-  getSeatsList();
-  // 接收消息
-  mittBus.on("monitorInfoTel", (data: any) => {
-    const item = seatsList.value.find(
-      (item: any) => item.Extension === data.Extension
-    );
-    item.loading = true;
-    if (item) {
-      setTimeout(() => {
-        item.State = data.State;
-        item.loading = false;
-      }, 500);
-    }
-  });
-  title.value = `${themeConfig.value.cityName}12345坐席监控中心`;
-  useTitle(title.value);
-});
-onUnmounted(() => {
-  signalR.SR.off("SeatState");
-});
-</script>
-
-<style scoped lang="scss">
-:deep(.el-badge__content) {
-  border: none;
-  top: -3px;
-  right: calc(-3px + var(--el-badge-size) / 2);
-}
-
-.title_wrap {
-  height: 140px;
-  position: relative;
-  &_btn {
-    position: absolute;
-    left: 40px;
-    top: 12px;
-    z-index: 2;
-    border-color: var(--el-color-white);
-  }
-  &::after {
-    content: "";
-    position: absolute;
-    bottom: 13px;
-    background-image: radial-gradient(circle, #1b86d1 50%, #176bb5 50%);
-    width: 100%;
-    height: 2px;
-  }
-
-  .guang {
-    position: absolute;
-    bottom: -13px;
-    background-image: url("../../assets/img/seats/guang.png");
-    background-position: 80px center;
-    width: 100%;
-    height: 56px;
-  }
-
-  .timers {
-    position: absolute;
-    right: 16px;
-    top: 20px;
-    display: flex;
-    align-items: center;
-    color: var(--el-color-white);
-    font-size: var(--el-font-size-extra-large);
-  }
-}
-
-.title {
-  position: relative;
-  text-align: center;
-  background-size: cover;
-  color: transparent;
-  height: 140px;
-  line-height: 90px;
-  &-text {
-    font-size: 38px;
-    font-weight: 900;
-    letter-spacing: 6px;
-    background: linear-gradient(
-      92deg,
-      #0072ff 0%,
-      #00eaff 48.8525390625%,
-      #01aaff 100%
-    );
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-  }
-}
-
-.left_icons {
-  position: absolute;
-  left: 40px;
-  bottom: 25px;
-  display: flex;
-  align-items: center;
-
-  .left_icons_item {
-    display: flex;
-    align-items: center;
-    margin-right: 35px;
-    &:last-child {
-      margin-right: 10px;
-    }
-    img {
-      margin-right: 5px;
-    }
-  }
-}
-
-.right_icons {
-  position: absolute;
-  right: 16px;
-  bottom: 25px;
-  display: flex;
-  align-items: center;
-  .right_icons_item {
-    display: flex;
-    align-items: center;
-    margin-right: 35px;
-    &:last-child {
-      margin-right: 10px;
-    }
-    img {
-      margin-right: 5px;
-    }
-  }
-}
-</style>

+ 0 - 629
src/views/seatMonitor/index.vue

@@ -1,629 +0,0 @@
-<template>
-  <scale-screen
-    width="1920"
-    height="1080"
-    :delay="500"
-    :fullScreen="true"
-    :boxStyle="{
-      overflow: themeConfig.isScale ? 'hidden' : 'auto',
-    }"
-    :autoScale="themeConfig.isScale"
-  >
-    <div class="content_wrap">
-      <Headers :data="seatsList" />
-      <Container :data="seatsList" />
-    </div>
-  </scale-screen>
-</template>
-<script setup lang="ts" name="seats">
-import { defineAsyncComponent, onMounted, ref } from "vue";
-import { storeToRefs } from "pinia";
-import { useThemeConfig } from "@/stores/themeConfig";
-import { useTimeoutFn, useWebSocket } from "@vueuse/core";
-import { useGlobalState } from "@/utils/callCenter";
-import { getNowDateTime } from "@/utils/constants";
-import { ElMessage } from "element-plus";
-import mittBus from "@/utils/mitt";
-
-const ScaleScreen = defineAsyncComponent(
-  () => import("@/components/Scale-screen/index.vue")
-);
-const Headers = defineAsyncComponent(
-  () => import("@/views/seatMonitor/header.vue")
-);
-const Container = defineAsyncComponent(
-  () => import("@/views/seatMonitor/container.vue")
-);
-
-const m_strUserNo = ref(""); // 分机号码
-const m_strUserName = ref(""); // 用户名称
-const m_strJobNum = ref("1"); // 坐席工号
-const m_strSkillId = ref("1"); // 技能组
-const m_strLevel = ref("1"); // 优先级别
-const m_strGroup = ref("1"); // 分组ID
-const m_strCompanyId = ref(""); // 企业编码
-const m_bLogin = ref(false); // 登录状态
-const m_bTelBusy = ref(false); // 是否示忙中
-const m_strIsMonitor = ref("1"); // 是否监控分机 1-是监控分机
-const callId = ref(""); // 通话ID
-const m_IsCallOut = ref(false); // 是否呼出
-const m_IsCallIn = ref(false); // 是否是呼入
-const m_strOpenFlag = ref("2"); // 来电弹屏方式 1-接通弹屏;2-振铃弹屏
-const m_CallOutOpen = ref(false); // 呼出是否弹屏(用于未接统计“回拨”业务处理)
-const m_bIsOpen = ref(false); // 是否已经弹屏
-const m_bCallConnect = ref(false); // 是否在通话状态
-const m_IsConsult = ref(false); // 是否咨询
-const m_strConsultType = ref("-1"); // 咨询类型
-const m_IsHangup = ref(false); // 是否挂机
-const m_IsHold = ref(false); // 是否保持
-const m_IsTalkingDeal = ref(false); // 是否通话整理
-const m_IsMonListen = ref("0"); // 监控状态 0-未监听;1-监控成功;2-监控失败;
-const m_strTelState = ref("0"); // 当前状态
-
-const globalState = useGlobalState(); // 全局变量
-const userAlreadyLogin = ref(false);
-// 发送消息
-const e_TelSendMsg = (strObj: Object) => {
-  // 客户端当前时间
-  const strMsg = JSON.stringify(strObj);
-  console.log(`${getNowDateTime()} 发送消息:`, strMsg, wsRef.value.status);
-  if (wsRef.value.ws?.readyState === 1) {
-    // 已经链接并且可以通讯,则发放文本消息
-    wsRef.value.send(strMsg);
-  } else {
-    ElMessage.error("请先签入");
-  }
-};
-// ws实例对象
-const wsRef = ref();
-const storesThemeConfig = useThemeConfig();
-const { themeConfig } = storeToRefs(storesThemeConfig);
-const initWs = () => {
-  // themeConfig.value.callCenterSocketUrl
-  wsRef.value = useWebSocket("ws://123.56.10.71:7681", {
-    heartbeat: {
-      message: JSON.stringify({
-        Action: "ReqHealthCheck",
-        Param: { Extension: m_strUserNo.value },
-      }),
-      interval: 5000,
-      pongTimeout: 5000,
-    },
-    autoReconnect: {
-      delay: 2000,
-    }, // 自动重连
-    immediate: true, // 是否立即链接
-    onMessage: e_TelMsgReceive, // 消息接收
-    onError: e_websocketError, // 错误
-    onDisconnected: e_websocketClose, // 断开
-    onConnected: e_websocketOpen, // 链接成功
-  });
-  // wsRef.value.open();
-};
-// 消息接收
-const e_TelMsgReceive = (ws: any, restMsg: any) => {
-  console.log(`${getNowDateTime()} 接收消息:${restMsg.data}`);
-  if (restMsg.data) {
-    const data = eval("(" + restMsg.data + ")");
-    if (data) {
-      // 方法
-      const strAction = data.Action;
-      switch (strAction) {
-        // 登录返回值
-        case "ResAgentLogin":
-          retSignIn(data);
-          break;
-        // 示闲
-        case "ResAgentIdle":
-          break;
-        // 示忙
-        case "ResAgentBusy":
-          break;
-        // 外呼状态
-        case "ResMakeCall":
-          break;
-        // 保持
-        case "ResHoldCall":
-          break;
-        // 取消保持
-        case "ResRetrieve":
-          break;
-        // 咨询内线
-        case "ResConsultInline":
-          break;
-        // 咨询外线
-        case "ResConsultOutline":
-          break;
-        // 咨询群组
-        case "ResConsultSkillGroup":
-          break;
-        // 咨询转移
-        case "ResTransfer":
-          break;
-        // 三方会议
-        case "ResConference":
-          break;
-        // 三方会议
-        case "ResMonConf":
-          break;
-        // 坐席实时状态
-        case "ResAgentMinitor":
-          ResAgentMonitor(data);
-          break;
-        // 监听
-        case "ResMonListen":
-          retResMonListen(data);
-          break;
-        // 取消监听返回
-        case "ResStopListen":
-          retResStopListen(data);
-          break;
-      }
-      // 事件
-      const strEvent = data.Event;
-      switch (strEvent) {
-        // 签出事件
-        case "EvtLogout":
-          retSignOut();
-          break;
-        // 呼入振铃事件
-        case "EvtCallAlerting":
-          break;
-        // 应答事件
-        case "EvtCallAnswer":
-          break;
-        // 挂机事件
-        case "EvtHangup":
-          break;
-        // 状态
-        case "EvtSeatState":
-          evtSeatState(data);
-          break;
-        // 队列等待
-        case "EvtAcdInfo":
-          break;
-        // 语音识别结果通知事件
-        case "EvtRecognize":
-          break;
-
-        // 转三方接通状态
-        case "EvtDispatchState":
-          break;
-
-        // 签出事件
-        case "ResStopMonitor":
-          retSignOut();
-          break;
-
-        case "ResError": // 异常
-          // 异常处理
-          retResError(data);
-          break;
-        // 呼出接通事件
-        case "EvtOutCalling":
-          break;
-        // 签出事件
-        case "EvtQuit":
-          retSignOut();
-          break;
-      }
-    }
-  }
-};
-const evtSeatState = (data: any) => {
-  if (m_strIsMonitor.value == "1") {
-    // 推送分机信息
-    const pushExt = data.Param.Extension || "";
-    if (pushExt !== m_strUserNo.value) {
-      // 如果分机实时推送的分机信息和当前登录分机不一致,则表明当前登录分机为监控分机,开启了状态监控功能
-      // 1.不属于当前分机的状态,则不改变当前分机电话条状态
-      // 2.调整其他分机大屏监控状态
-      const pushState = GetTelState(data.Param.State);
-      if (pushState) {
-        // 修改后台数据
-        console.log(pushExt, pushState);
-        // SetMonitorState(pushExt, pushState);
-      }
-      mittBus.emit("monitorInfoTel", data.Param);
-      return;
-    }
-  }
-  if (m_IsHold.value) {
-    // 正在保持通话状态下
-    return;
-  }
-  // 状态 0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话
-  const strState = data.Param.State;
-  switch (strState) {
-    // 空闲
-    case "0":
-      m_bIsOpen.value = false;
-      m_IsHangup.value = false;
-      m_strTelState.value = "200";
-      m_bTelBusy.value = false;
-      e_TopStateChange(m_strTelState.value);
-      m_IsTalkingDeal.value = false;
-      break;
-    // 示忙
-    case "1":
-      m_strTelState.value = "201";
-      m_bTelBusy.value = true;
-      e_TopStateChange(m_strTelState.value);
-      break;
-    case "2":
-      break;
-    // 登出
-    case "3":
-      // 附加签出方法(用户分机分离调用业务系统签出)
-      m_strTelState.value = "0";
-      e_TopStateChange(m_strTelState.value);
-      break;
-    // 通话振铃
-    case "4":
-      if (!m_IsHangup.value) {
-        if (m_IsCallOut.value) {
-          // 呼出振铃
-          m_strTelState.value = "302";
-        } else {
-          // 呼入振铃
-          m_strTelState.value = "300";
-        }
-        e_TopStateChange(m_strTelState.value);
-      }
-      break;
-    // 通话振铃
-    case "5":
-      if (!m_IsHangup.value) {
-        m_strTelState.value = "302";
-        e_TopStateChange(m_strTelState.value);
-      }
-      break;
-    // 咨询
-    case "6":
-      break;
-    // 其他
-    case "7":
-      break;
-    // 接通
-    case "8":
-      if (!m_IsHangup.value) {
-        if (m_IsCallOut.value) {
-          // 呼出接通
-          m_strTelState.value = "303";
-        } else {
-          // 呼入接通
-          m_strTelState.value = "301";
-          // 是否是保持通话
-        }
-        e_TopStateChange(m_strTelState.value);
-      }
-      break;
-    case "9": // 工单整理
-      m_strTelState.value = "900";
-      e_TopStateChange(m_strTelState.value);
-      m_IsTalkingDeal.value = true;
-      break;
-  }
-};
-// 获取分机状态
-const GetTelState = (strState: any) => {
-  let strResult = "";
-  switch (strState) {
-    // 空闲
-    case "0":
-      strResult = "200";
-      break;
-    // 示忙
-    case "1":
-      strResult = "201";
-      break;
-    case "2":
-      break;
-    // 登出
-    case "3":
-      strResult = "0";
-      break;
-    // 通话振铃
-    case "4":
-      strResult = "302"; // 默认呼入振铃
-      break;
-    // 通话振铃
-    case "5":
-      strResult = "302"; // 默认呼出振铃
-      break;
-    // 咨询
-    case "6":
-      break;
-    // 其他
-    case "7":
-      break;
-    // 接通
-    case "8":
-      strResult = "301";
-      break;
-    case "9": // 工单整理
-      strResult = "900";
-      break;
-  }
-  return strResult;
-};
-// 不登录开启坐席监控
-const sendMonitor = () => {
-  const sendObj = {
-    Action: "ReqStateMonitor",
-    Param: {
-      MonitorId: new Date().getTime(),
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(sendObj);
-  console.log(`${getNowDateTime()} 呼叫中心开启监控消息`);
-};
-// ws链接开启成功
-const e_websocketOpen = () => {
-  /*  if (userAlreadyLogin.value) {
-    // 检查到用户已经登录需要先签出 再签入
-    sendSignOut();
-    useTimeoutFn(() => {
-      sendSignIn();
-    }, 500);
-
-  //   {"Action" : "ReqStateMonitor", "Param":  { "MonitorId":""}}
-  } else {
-    if (m_strUserNo.value && m_strSkillId.value) sendSignIn();
-  }*/
-  sendMonitor(); // 开启监控
-};
-// 链接关闭
-const e_websocketClose = () => {
-  globalState.callCenterIsSignIn = false; // 签出状态
-  console.log(`${getNowDateTime()} 呼叫中心链接关闭`);
-};
-// 链接错误
-const e_websocketError = () => {
-  globalState.callCenterWs = null;
-  globalState.callCenterIsSignIn = false; // 签出状态
-  console.log(`${getNowDateTime()} 呼叫中心链接错误`);
-};
-/*
-* 登录
-* ReqAgentLogin - 登录方法名
-* JobNum - 工号
-* Name - 姓名
-* Extension - 分机号
-* SkillId - 技能组
-* Level - 为要设置的级别,分为9个级别,从高到低分别为0-8;同一技能组中级别越高的坐席优先被分配
-* Role - 角色,保留,不做设置
-* GroupName - 技能组名称
-* OrgId - 组织ID
-* 返回内容:
-{“Action”:”ResAgentLogin”,”Param”:{“Result”:}}
-Result: 3:分机错误 7:已登录 0:登录成功
- */
-const sendSignIn = () => {
-  globalState.callCenterWs = wsRef.value;
-  const sendObj = {
-    Action: "ReqAgentLogin",
-    Param: {
-      JobNum: m_strJobNum.value,
-      Name: m_strUserName.value,
-      Extension: m_strUserNo.value,
-      SkillId: m_strSkillId.value,
-      Level: m_strLevel.value,
-      Role: "",
-      GroupName: m_strGroup.value,
-      OrgId: m_strCompanyId.value,
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(sendObj);
-  console.log(`${getNowDateTime()} 呼叫中心发起签入`);
-};
-// 签入消息回调
-const retSignIn = (data: any) => {
-  if (data.Param.Result === "0") {
-    // 登录成功
-    m_bLogin.value = true;
-    m_strTelState.value = "100";
-
-    e_TopStateChange(m_strTelState.value);
-    // 登录成功
-    globalState.callCenterIsSignIn = true; // 签入状态
-    if (m_strIsMonitor.value === "1") {
-      // 监控初始化状态
-      ReqAgentMonitor();
-    }
-    console.log(`${getNowDateTime()} 呼叫中心签入成功回调`);
-  } else if (data.Param.Result === "3") {
-    // 分机错误
-    ElMessage.error("分机错误");
-    userAlreadyLogin.value = false; // 将登录状态重置
-    wsRef.value.close();
-    // 分机错误
-  } else if (data.Param.Result === "7") {
-    // 已经处于登录状态
-    // 先签出再签入
-    /* sendSignOut();
-    userAlreadyLogin.value = false; // 将登录状态重置*/
-    wsRef.value.close();
-    ElMessage.error("当前分机已经签入");
-  }
-};
-/**
- * 状态初始化请求 监听
- * */
-const ReqAgentMonitor = () => {
-  // 开始坐席状态监控
-  // SkillId 技能组为0则监控所有分机
-  const msgObj = {
-    Action: "ReqAgentMonitor",
-    Param: {
-      Extension: m_strUserNo.value,
-      CompanyId: m_strCompanyId.value,
-      SkillId: "0",
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(msgObj);
-};
-/**
- * {“Action”:”ResAgentMonitor”,”Param”:[{“Extension”:,”JobNumber”:,”SkillId”:,”Name”:,”Caller”:,”Called”:,”State”}]}
- * State:0:闲 1:忙 2:会议 3:登出 4:呼入 5:呼出 6:咨询 7:其他 8:通话 9:工单整理
- * 监控状态初始化返回状态
- * @param {any} data
- */
-const seatsList = ref<any[]>([]);
-const ResAgentMonitor = (data: any) => {
-  if (null != data && null != data.Param && data.Param.length > 0) {
-    // 监控分机集合
-    const strMonitorInfo = JSON.stringify(data.Param);
-    console.log(
-      `${getNowDateTime()} 监控分机集合:`,
-      strMonitorInfo,
-      data.Param
-    );
-    mittBus.emit("monitorInfo", data.Param);
-    seatsList.value = data.Param;
-  }
-};
-/*
- * 签出
- * ReqAgentLogout - 签出方法名称
- * Extension - 分机号码
- */
-const sendSignOut = () => {
-  const objMsg = {
-    Action: "ReqAgentLogout",
-    Param: {
-      Extension: m_strUserNo.value,
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(objMsg);
-};
-/*
- * 签出事件
- */
-const retSignOut = () => {
-  // 登出成功
-  m_strTelState.value = "0";
-  m_bLogin.value = false;
-  globalState.callCenterIsSignIn = false; // 签出状态
-  globalState.callCenterWs = null;
-  wsRef.value.close();
-  // 如果用户没有登录 关闭ws
-  if (!userAlreadyLogin.value) {
-    wsRef.value.close();
-  }
-  console.log(`${getNowDateTime()} 呼叫中心签出回调`);
-};
-/**
- * 异常处理
- * @param {any} data
- */
-const retResError = (data: any) => {
-  if (data.Param.Result == "99") {
-    // 掉线
-    m_strTelState.value = "0";
-    e_TopStateChange(m_strTelState.value);
-    globalState.callCenterWs = null;
-    ElMessage.error("连接已断开");
-    // 自动签入
-  }
-};
-// 改变状态方法
-const e_TopStateChange = (state: string) => {
-  console.log(`${getNowDateTime()}:状态改变:`, state);
-  switch (state) {
-    case "0": // 签出
-      break;
-    case "100": // 登录成功
-      break;
-    case "200": // 空闲
-      break;
-    case "201": // 示忙
-      break;
-    case "300": //呼入振铃
-      break;
-    case "301": // 呼入通话
-      break;
-    case "302": // 呼出振铃
-      break;
-    case "303": // 呼出通话
-      break;
-    case "310": // 通话保持
-      break;
-    case "320": // 三方会议
-      break;
-    case "330": // 转接
-      break;
-    case "331": // 转接
-      break;
-    case "900": // 整理
-      break;
-  }
-  // console.log(state);
-};
-/*
- * 监听
- */
-const reqMonListen = (strTargetNum: string) => {
-  const objMsg = {
-    Action: "ReqMonListen",
-    Param: {
-      Extension: m_strUserNo.value,
-      TargetExtension: strTargetNum,
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(objMsg);
-};
-/*
- * 监听返回
- */
-const retResMonListen = (data: any) => {
-  if (data.Param.Result == "0") {
-    m_IsMonListen.value = "1";
-    // 成功
-  } else {
-    m_IsMonListen.value = "2";
-  }
-};
-/*
- * 取消监听
- */
-const reqStopListen = (strTargetNum: string) => {
-  const objMsg = {
-    Action: "ReqStopListen",
-    Param: {
-      Extension: m_strUserNo.value,
-      TargetExtension: strTargetNum,
-    },
-  };
-  // 发送请求
-  e_TelSendMsg(objMsg);
-};
-/*
- * 取消监听返回
- */
-const retResStopListen = (data: any) => {
-  if (data.Param.Result == "0") {
-    m_IsMonListen.value = "1";
-    // 成功
-  } else {
-    m_IsMonListen.value = "2";
-  }
-};
-onMounted(async () => {
-  initWs();
-});
-</script>
-<style lang="scss" scoped>
-.content_wrap {
-  width: 100%;
-  height: 100%;
-  box-sizing: border-box;
-  background-image: url("@/assets/img/seats/bg.png");
-  background-size: 100% 100%;
-}
-</style>

+ 0 - 293
src/views/seatMonitor/left-bottom.vue

@@ -1,293 +0,0 @@
-<template>
-  <div class="left_bottom_wrap">
-    <div class="left_number">
-      <div>
-        <span>登录坐席数量:</span>
-        <CountUp :endVal="loginCount" />
-      </div>
-      <div>
-        <span>呼入接通数量:</span>
-        <CountUp :endVal="state.count.inOn" />
-      </div>
-      <div>
-        <span>有效接通数量:</span>
-        <CountUp :endVal="state.count.validOn" />
-      </div>
-      <div>
-        <span>未接通数量:</span>
-        <CountUp :endVal="state.count.inNoOn" />
-      </div>
-      <div>
-        <span>外呼接通数量:</span>
-        <CountUp :endVal="state.count.outOn" />
-      </div>
-      <div>
-        <span>队列挂断数量:</span>
-        <CountUp :endVal="state.count.inQueueNoOn" />
-      </div>
-    </div>
-    <div class="right_chart">
-      <v-chart class="chart" :option="option" />
-    </div>
-  </div>
-</template>
-<script setup lang="ts">
-import {
-  onMounted,
-  reactive,
-  ref,
-  computed,
-  onUnmounted,
-  watch,
-  defineAsyncComponent,
-} from "vue";
-import signalR from "@/utils/signalR";
-
-const CountUp = defineAsyncComponent(
-  () => import("@/components/Count-up/index.vue")
-);
-const props = defineProps({
-  data: {
-    type: Array,
-    default: () => [],
-  },
-  content: {
-    type: Object,
-    default: () => {},
-  },
-});
-const option = ref<EmptyObjectType>({});
-const state = reactive<any>({
-  count: {
-    inNoOn: 0,
-    inOn: 0,
-    inQueueNoOn: 0,
-    outOn: 0,
-    validOn: 0,
-  },
-});
-const setOption = async (newData: any) => {
-  option.value = {
-    tooltip: {
-      trigger: "axis",
-      backgroundColor: "rgba(0,0,0,.6)",
-      borderColor: "rgba(147, 235, 248, .8)",
-      textStyle: {
-        color: "#FFF",
-      },
-    },
-    legend: {
-      data: ["呼入", "外呼", "外呼平均", "呼入平均"],
-      textStyle: {
-        color: "#fff",
-      },
-      top: "0",
-      left: "80px",
-      icon: "roundRect",
-      itemWidth: 16,
-      itemHeight: 16,
-      itemGap: 20,
-    },
-    grid: {
-      left: "50px",
-      right: "10px",
-      bottom: "30px",
-      top: "30px",
-    },
-    xAxis: {
-      data: newData.xData,
-      axisLine: {
-        lineStyle: {
-          color: "#fff",
-        },
-      },
-      axisTick: {
-        show: false,
-      },
-    },
-    yAxis: [
-      {
-        splitLine: { show: false },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-        axisLabel: {
-          formatter: "{value}",
-        },
-      },
-      {
-        splitLine: { show: false },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-        axisLabel: {
-          formatter: "{value}% ",
-        },
-      },
-    ],
-    series: [
-      {
-        name: "呼入",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        data: newData.inData,
-      },
-      {
-        name: "外呼",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        z: -12,
-        data: newData.outData,
-      },
-      {
-        name: "呼入平均",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        z: -12,
-        data: newData.inAverageData,
-      },
-      {
-        name: "外呼平均",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        z: -12,
-        data: newData.outAverageData,
-      },
-    ],
-  };
-};
-const seatsList = ref<any>([]);
-// 登录坐席数量
-const loginCount = computed(() => {
-  return seatsList.value.filter((item: any) => item.state !== "logout").length;
-});
-watch(
-  () => props.content,
-  (val: any) => {
-    state.count = val.count[0];
-    let data = {
-      xData: [],
-      inData: [],
-      outData: [],
-      inAverageData: [],
-      outAverageData: [],
-    };
-    val.list.forEach((item: any) => {
-      data.xData.push(item.time);
-      data.inData.push(item.in);
-      data.outData.push(item.out);
-      data.inAverageData.push(item.inAverag);
-      data.outAverageData.push(item.outAverag);
-    });
-    setTimeout(() => {
-      setOption(data);
-    }, 100);
-  }
-);
-watch(
-  () => props.data,
-  (newData: any) => {
-    seatsList.value = newData;
-  },
-  { immediate: true }
-);
-onMounted(() => {
-  signalR.SR.on("SeatState", (res: any) => {
-    const item = seatsList.value.find((item: any) => item.telNo === res.telNo);
-    if (item) {
-      item.state = res.state;
-    }
-  });
-  signalR.SR.on("BsSeatStateDataShowArea3", (res: any) => {
-    state.count = res[0];
-  });
-  signalR.SR.on("BsSeatStateDataShowArea4", (res: any) => {
-    let data = {
-      xData: [],
-      inData: [],
-      outData: [],
-      inAverageData: [],
-      outAverageData: [],
-    };
-    res.forEach((item: any) => {
-      data.xData.push(item.time);
-      data.inData.push(item.in);
-      data.outData.push(item.out);
-      data.inAverageData.push(item.inAverag);
-      data.outAverageData.push(item.outAverag);
-    });
-    setTimeout(() => {
-      setOption(data);
-    }, 100);
-  });
-});
-onUnmounted(() => {
-  signalR.SR.off("SeatState");
-  signalR.SR.off("BsSeatStateDataShowArea3");
-  signalR.SR.off("BsSeatStateDataShowArea4");
-});
-</script>
-<style scoped lang="scss">
-.left_bottom_wrap {
-  overflow: hidden;
-  width: 100%;
-  height: 100%;
-  display: flex;
-  font-size: var(--el-font-size-base);
-
-  .left_number {
-    margin: 20px 0;
-    height: calc(100% - 40px);
-    display: flex;
-    flex-direction: column;
-    justify-content: space-around;
-    min-width: 130px;
-
-    div {
-      display: flex;
-      justify-content: space-between;
-    }
-  }
-
-  .right_chart {
-    flex: 1;
-    height: 100%;
-  }
-}
-</style>

+ 0 - 171
src/views/seatMonitor/left-center.vue

@@ -1,171 +0,0 @@
-<template>
-  <div class="top10">
-    <template v-if="data.length">
-      <div v-for="(item, index) in data" :key="index" class="item">
-        <span class="num" :class="'num' + index">
-          {{ index + 1 }}
-        </span>
-        <span>
-          {{ item.userName }}
-        </span>
-        <CountUp :endVal="item.in" :duration="3" />
-      </div>
-    </template>
-    <empty v-else />
-  </div>
-</template>
-<script setup lang="ts">
-import { ref, onMounted, onUnmounted, watch, defineAsyncComponent } from "vue";
-import signalR from "@/utils/signalR";
-
-const Empty = defineAsyncComponent(
-  () => import("@/components/Empty/index.vue")
-);
-const CountUp = defineAsyncComponent(
-  () => import("@/components/Count-up/index.vue")
-);
-const data = ref<EmptyArrayType>([]);
-const props = defineProps({
-  content: {
-    type: Array,
-    default: () => [],
-  },
-});
-watch(
-  () => props.content,
-  (newData: any) => {
-    data.value = newData;
-  }
-);
-onMounted(() => {
-  signalR.SR.on("BsSeatStateDataShowArea2", (res: any) => {
-    data.value = res;
-  });
-});
-onUnmounted(() => {
-  signalR.SR.off("BsSeatStateDataShowArea2");
-});
-</script>
-<style scoped lang="scss">
-.top10 {
-  margin-top: 10px;
-  height: calc(100% - 10px);
-  display: flex;
-  flex-direction: column;
-  transition: max-height 0.3s ease-in;
-  transform-origin: 50% 0;
-  animation: slide-down 0.3s ease-in;
-
-  .item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 0 10px;
-
-    &:nth-child(even) {
-      background-color: rgba(255, 255, 255, 0.1);
-    }
-  }
-
-  .item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    height: 30px;
-    line-height: 30px;
-    padding: 0 10px;
-
-    &:nth-child(even) {
-      background-color: rgba(255, 255, 255, 0.1);
-    }
-
-    .num {
-      display: inline-block;
-      width: 30px;
-      height: 20px;
-      line-height: 20px;
-      text-align: center;
-      color: #fff;
-      background-color: var(--el-color-primary);
-      border-radius: 5px;
-
-      &:before {
-        content: "";
-        width: 0;
-        height: 0;
-        border-top: 10px solid transparent;
-        border-bottom: 10px solid transparent;
-        border-left: 10px solid var(--el-color-primary);
-        position: absolute;
-        left: 52px;
-      }
-    }
-
-    .num0 {
-      display: inline-block;
-      width: 30px;
-      height: 20px;
-      line-height: 20px;
-      text-align: center;
-      color: #fff;
-      background-color: var(--el-color-danger);
-      border-radius: 5px;
-
-      &:before {
-        content: "";
-        width: 0;
-        height: 0;
-        border-top: 10px solid transparent;
-        border-bottom: 10px solid transparent;
-        border-left: 10px solid var(--el-color-danger);
-        position: absolute;
-        left: 52px;
-      }
-    }
-
-    .num1 {
-      display: inline-block;
-      width: 30px;
-      height: 20px;
-      line-height: 20px;
-      text-align: center;
-      color: #fff;
-      background-color: var(--el-color-warning);
-      border-radius: 5px;
-
-      &:before {
-        content: "";
-        width: 0;
-        height: 0;
-        border-top: 10px solid transparent;
-        border-bottom: 10px solid transparent;
-        border-left: 10px solid var(--el-color-warning);
-        position: absolute;
-        left: 52px;
-      }
-    }
-
-    .num2 {
-      display: inline-block;
-      width: 30px;
-      height: 20px;
-      line-height: 20px;
-      text-align: center;
-      color: #fff;
-      background-color: var(--el-color-success);
-      border-radius: 5px;
-
-      &:before {
-        content: "";
-        width: 0;
-        height: 0;
-        border-top: 10px solid transparent;
-        border-bottom: 10px solid transparent;
-        border-left: 10px solid var(--el-color-success);
-        position: absolute;
-        left: 52px;
-      }
-    }
-  }
-}
-</style>

+ 0 - 150
src/views/seatMonitor/left-top.vue

@@ -1,150 +0,0 @@
-<template>
-  <v-chart class="chart" :option="option" />
-</template>
-<script setup lang="ts">
-import { onMounted, ref, onUnmounted, watch } from "vue";
-import signalR from "@/utils/signalR";
-
-const props = defineProps({
-  content: {
-    type: Array,
-    default: () => [],
-  },
-});
-const option = ref({});
-const setOption = async (newData: any) => {
-  option.value = {
-    tooltip: {
-      trigger: "axis",
-      backgroundColor: "rgba(0,0,0,.6)",
-      borderColor: "rgba(147, 235, 248, .8)",
-      textStyle: {
-        color: "#FFF",
-      },
-    },
-    legend: {
-      data: ["呼入", "外呼"],
-      textStyle: {
-        color: "#fff",
-      },
-      top: "0",
-    },
-    grid: {
-      left: "40px",
-      right: "10px",
-      bottom: "30px",
-      top: "30px",
-    },
-    xAxis: {
-      data: newData.xData,
-      axisLine: {
-        lineStyle: {
-          color: "#fff",
-        },
-      },
-      axisTick: {
-        show: false,
-      },
-    },
-    yAxis: [
-      {
-        splitLine: { show: false },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-        axisLabel: {
-          formatter: "{value}",
-        },
-      },
-      {
-        splitLine: { show: false },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-        axisLabel: {
-          formatter: "{value}% ",
-        },
-      },
-    ],
-    series: [
-      {
-        name: "呼入",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        data: newData.inData,
-      },
-      {
-        name: "外呼",
-        type: "line",
-        symbol: "circle", //数据交叉点样式
-        areaStyle: {
-          //添加背景颜色
-          opacity: 0.3, //透明度
-        },
-        itemStyle: {
-          borderRadius: 5,
-        },
-        smooth: true,
-        z: -12,
-        data: newData.outData,
-      },
-    ],
-  };
-};
-watch(
-  () => props.content,
-  (val: any) => {
-    let data = {
-      xData: [],
-      inData: [],
-      outData: [],
-    };
-    val.forEach((item: any) => {
-      data.xData.push(item.time);
-      data.inData.push(item.in);
-      data.outData.push(item.out);
-    });
-    setTimeout(() => {
-      setOption(data);
-    }, 100);
-  }
-);
-onMounted(() => {
-  signalR.SR.on("BsSeatStateDataShowArea1", (res: any) => {
-    let data = {
-      xData: [],
-      inData: [],
-      outData: [],
-    };
-    res.forEach((item: any) => {
-      data.xData.push(item.time);
-      data.inData.push(item.in);
-      data.outData.push(item.out);
-    });
-    setTimeout(() => {
-      setOption(data);
-    }, 100);
-  });
-});
-onUnmounted(() => {
-  signalR.SR.off("BsSeatStateDataShowArea1");
-});
-</script>
-<style scoped lang="scss">
-.left-top {
-  width: 100%;
-  height: 100%;
-}
-</style>

+ 0 - 385
src/views/seatMonitor/right.vue

@@ -1,385 +0,0 @@
-<template>
-  <div
-    class="seats_box"
-    v-loading="loading"
-    element-loading-text="加载中..."
-    element-loading-svg-view-box="-10, -10, 50, 50"
-    element-loading-background="rgba(122, 122, 122, 0.1)"
-  >
-    <div class="seats_container">
-      <div
-        v-for="(item, index) in seatsList"
-        class="seats_item"
-        ref="textRefs"
-        @click="handleClick(index, item)"
-      >
-        <img
-          src="@/assets/img/seats/service.png"
-          alt=""
-          class="seats_item_service"
-        />
-        <p class="seats_item_tel">{{ item.Extension }}</p>
-        <p class="seats_item_name" v-if="item.State === '3'">未登录</p>
-        <p class="seats_item_name" v-else>
-          {{ item.Name ? item.Name : "未知" }}
-        </p>
-        <span @click.stop>
-          <el-dropdown
-            @command="handleCommand($event, item)"
-            class="seats_item_dropdown"
-            trigger="click"
-            v-if="item.state === 'busy' && globalState.callCenterIsSignIn"
-          >
-            <el-icon class="seats_item_more" size="18">
-              <Operation />
-            </el-icon>
-            <template #dropdown>
-              <el-dropdown-menu>
-                <el-dropdown-item command="listen">监 听</el-dropdown-item>
-                <el-dropdown-item command="interject">插 话</el-dropdown-item>
-              </el-dropdown-menu>
-            </template>
-          </el-dropdown>
-        </span>
-        <div class="seats_item_state">
-          <img :src="currentStatusImg(item.State)" alt="" />
-          <span class="seats_item_telNo">
-            {{ currentStatusText(item.State) }}</span
-          >
-        </div>
-      </div>
-    </div>
-
-    <el-popover
-      ref="popoverRef"
-      :virtual-ref="textRef"
-      :visible="hidePopover"
-      virtual-triggering
-      placement="right-start"
-      @hide="popoverHide"
-      width="220"
-      popper-class="call_popover_popper"
-    >
-      <template #default>
-        <div v-click-outside="onClickOutside" class="call_popover">
-          <div class="call_popover_item">
-            <span class="label">坐席名称:</span>{{ call.Name ?? "未知" }}
-          </div>
-          <div class="call_popover_item">
-            <span class="label">分机号:</span>{{ call.Extension }}
-          </div>
-          <div class="call_popover_item">
-            <span class="label">通话时长:</span
-            ><span v-if="talkTime">{{ formatDuration(talkTime) }}</span>
-          </div>
-          <div class="call_popover_item">
-            <span class="label">呼入类型:</span>
-            <el-text v-if="call.callDirection === 'inbound'">呼入</el-text>
-            <el-text v-if="call.callDirection === 'outbound'">外呼</el-text>
-            <i></i>
-          </div>
-          <div class="call_popover_item">
-            <span class="label">电话号码:</span>{{ call.otherNumber }}
-          </div>
-          <div class="call_popover_item">
-            <span class="label">今日接听量:</span>{{ call.onStateCount }}
-          </div>
-        </div>
-      </template>
-    </el-popover>
-  </div>
-</template>
-<script setup lang="ts">
-import { onMounted, ref, nextTick, onBeforeUnmount, watch } from "vue";
-import { getImageUrl } from "@/utils/tools";
-import signalR from "@/utils/signalR";
-import { ClickOutside as vClickOutside } from "element-plus";
-import { formatDuration } from "@/utils/formatTime";
-import dayjs from "dayjs";
-import { getExtensionStatus } from "api/seats";
-import { useIntervalFn } from "@vueuse/core";
-import { useGlobalState } from "@/utils/callCenter";
-import { Operation } from "@element-plus/icons-vue";
-import mittBus from "@/utils/mitt";
-
-const props = defineProps({
-  data: {
-    type: Array,
-    default: () => [],
-  },
-});
-
-const textRefs = ref<EmptyArrayType>([]);
-const textRef = ref();
-const popoverRef = ref();
-const hidePopover = ref(false);
-const call = ref<EmptyObjectType>({});
-const globalState = useGlobalState();
-// 开始签入时长
-const talkTime = ref<any>(0); // 通话时长
-const talkTimer = ref<any>(null); // 通话时长定时器
-const startCallTimer = (second: any) => {
-  if (second) {
-    // 从后台获取签入时长
-    if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
-    talkTime.value = second;
-    talkTimer.value = useIntervalFn(() => {
-      talkTime.value++;
-    }, 1000);
-  } else {
-    talkTimer.value = useIntervalFn(() => {
-      talkTime.value++;
-    }, 1000);
-  }
-};
-const handleClick = (index: number, item: { state: string; telNo: string }) => {
-  textRef.value = textRefs.value[index];
-  call.value = item;
-  talkTime.value = 0;
-  if (talkTimer.value) talkTimer.value.pause();
-  if (["busy", "held"].includes(item.state)) {
-    getExtensionStatus({ telno: item.telNo }).then((res: any) => {
-      hidePopover.value = true;
-      call.value = res.result;
-      // 获取现在时间与签入时间的秒数
-      talkTime.value = dayjs().diff(dayjs(res.result.answeredAt), "second");
-      startCallTimer(talkTime.value);
-    });
-  } else {
-    talkTime.value = 0;
-    call.value.otherNumber = "";
-  }
-};
-// 隐藏弹窗
-const popoverHide = () => {
-  if (talkTimer.value) talkTimer.value.pause();
-};
-const onClickOutside = () => {
-  hidePopover.value = false;
-};
-// 设置当前状态的值
-const currentStatusText = (state: string) => {
-  const statusMap: any = {
-    "0": "示闲",
-    "1": "小休",
-    "2": "三方会议",
-    "3": "签出",
-    "4": "呼入",
-    "5": "呼出",
-    "6": "咨询",
-    "7": "其他",
-    "8": "通话",
-    "9": "整理",
-    "10": "注销",
-  };
-  return statusMap[state] || "";
-};
-// 状态映射图片地址
-const currentStatusImg = (state: string) => {
-  const statusMap: any = {
-    "0": "ready",
-    "1": "unready",
-    "2": "threeWay",
-    "3": "logout",
-    "4": "callIn",
-    "5": "callOut",
-    "6": "ring",
-    "7": "other",
-    "8": "busy",
-    "9": "acw",
-    "10": "logoff",
-  };
-  return getImageUrl("seats/" + statusMap[state] + ".png");
-};
-const seatsList = ref<any>([]);
-watch(
-  () => props.data,
-  (newData: any) => {
-    seatsList.value = newData;
-    if (newData.length) {
-      setTimeout(() => {
-        sortSeatsList();
-      }, 1000);
-    }
-  },
-  { immediate: true }
-);
-const loading = ref(false);
-// 对当前状态进行排序
-const sortSeatsList = () => {
-  // 呼入
-  const callInData = seatsList.value.filter((item: any) => item.State === "4");
-  // 呼出
-  const callOutData = seatsList.value.filter((item: any) => item.State === "5");
-  // 通话
-  const callData = seatsList.value.filter((item: any) => item.State === "8");
-  // 会议
-  const meetingData = seatsList.value.filter((item: any) => item.State === "2");
-  // 咨询
-  const consultData = seatsList.value.filter((item: any) => item.State === "6");
-  // 小休
-  const restData = seatsList.value.filter((item: any) => item.State === "1");
-  // 示闲
-  const readyData = seatsList.value.filter((item: any) => item.State === "0");
-  // 签出
-  const logoutData = seatsList.value.filter((item: any) => item.State === "3");
-  // 其他
-  const otherData = seatsList.value.filter((item: any) => item.State === "7");
-  // 整理
-  const acwData = seatsList.value.filter((item: any) => item.State === "9");
-  // 注销
-  const logoffDta = seatsList.value.filter((item: any) => item.State === "10");
-  seatsList.value = [
-    ...callInData,
-    ...callOutData,
-    ...callData,
-    ...meetingData,
-    ...consultData,
-    ...acwData,
-    ...restData,
-    ...readyData,
-    ...logoutData,
-    ...logoffDta,
-    ...otherData,
-  ];
-};
-onMounted(async () => {
-  await nextTick();
-  // await getSeatsList();
-  // 接收消息
-  signalR.SR.on("SeatState", (res: any) => {
-    const item = seatsList.value.find((item: any) => item.telNo === res.telNo);
-    if (item) {
-      setTimeout(() => {
-        item.state = res.state;
-        item.workUserName = res.workUserName;
-        item.workUserId = res.workUserId;
-        item.loading = false;
-        sortSeatsList();
-        // hidePopover.value = false;
-      }, 500);
-    }
-  });
-  mittBus.on("monitorInfoTel", (data: any) => {
-    const item = seatsList.value.find(
-      (item: any) => item.Extension === data.Extension
-    );
-    if (item) {
-      setTimeout(() => {
-        item.State = data.State;
-        // item.Name = data.Name;
-        // item.workUserId = data.workUserId;
-        item.loading = false;
-        sortSeatsList();
-        // hidePopover.value = false;
-      }, 500);
-    }
-  });
-});
-// 监听和插话消息
-const handleCommand = (command: string, item: any) => {
-  console.log(command, item);
-  if (command === "listen") {
-    // 监听
-    globalState.callCenterWs.monitor(
-      item.telNo,
-      globalState.callCenterWs.username
-    );
-  } else if (command === "interject") {
-    // 强插
-    globalState.callCenterWs.intercept(
-      item.telNo,
-      globalState.callCenterWs.username
-    );
-  }
-};
-onBeforeUnmount(() => {
-  signalR.SR.off("SeatState");
-});
-</script>
-<style scoped lang="scss">
-.seats_box {
-  width: 100%;
-  overflow: auto;
-  height: 100%;
-
-  .seats_container {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, 100px);
-    grid-gap: 20px;
-
-    .seats_item {
-      width: 100px;
-      border: 1px solid var(--el-color-primary-light-3);
-      border-radius: 8px;
-      text-align: center;
-      padding: 7px 0;
-      animation: bounce-out 0.3s ease;
-      position: relative;
-      cursor: pointer;
-      &_service {
-        margin: 0 auto;
-        width: 40px;
-      }
-
-      &_tel {
-        font-size: var(--el-font-size-medium);
-        color: var(--el-color-white);
-        margin-top: 8px;
-      }
-
-      &_dropdown {
-        position: absolute;
-        top: 5px;
-        right: 10px;
-        outline: none;
-      }
-
-      &_more {
-        cursor: pointer;
-      }
-
-      &_name {
-        font-size: var(--el-font-size-medium);
-        color: var(--el-color-white);
-        margin-top: 5px;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        padding: 0 5px;
-      }
-
-      &_state {
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin-top: 8px;
-
-        img {
-          width: 25px;
-          height: 25px;
-        }
-
-        .seats_item_telNo {
-          font-size: var(--el-font-size-medium);
-          color: var(--el-color-white);
-          margin-left: 5px;
-        }
-      }
-    }
-  }
-}
-
-.call_popover_popper {
-  .call_popover {
-    .call_popover_item {
-      margin-bottom: 5px;
-      .label {
-        display: inline-block;
-        width: 90px;
-        text-align: right;
-      }
-    }
-  }
-}
-</style>