123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- <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.telNo }}</p>
- <p class="seats_item_name" v-if="item.state === 'logout'">未登录</p>
- <p class="seats_item_name" v-else>
- {{ item.workUserName ? item.workUserName : "未知" }}
- </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="getImageUrl('seats/' + item.state + '.png')" 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.workUserName }}
- </div>
- <div class="call_popover_item">
- <span class="label">分机号:</span>{{ call.telNo }}
- </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 = {
- logout: "签出",
- login: "签入",
- ready: "示闲",
- unready: "小休",
- ring: "振铃中",
- busy: "通话中",
- acw: "整理",
- held: "保持",
- treeWay: "三方会议",
- };
- return statusMap[state] || "";
- };
- const seatsList = ref<any>([]);
- watch(
- () => props.data,
- (newData: any) => {
- seatsList.value = newData;
- },
- { immediate: true }
- );
- const loading = ref(false);
- // 对当前状态进行排序
- const sortSeatsList = () => {
- // 通话中的排序
- const busyData = seatsList.value.filter((item: any) => item.state === "busy");
- // 通话中的排序
- const ringData = seatsList.value.filter((item: any) => item.state === "ring");
- // 话后整理
- const acwData = seatsList.value.filter((item: any) => item.state === "acw");
- // 保持
- const heldData = seatsList.value.filter((item: any) => item.state === "held");
- // 三方会议
- const treeWayData = seatsList.value.filter(
- (item: any) => item.state === "treeWay"
- );
- // 小休
- const unreadyData = seatsList.value.filter(
- (item: any) => item.state === "unready"
- );
- // 示闲
- const readyData = seatsList.value.filter(
- (item: any) => item.state === "ready"
- );
- // 签出
- const logoutData = seatsList.value.filter(
- (item: any) => item.state === "logout"
- );
- seatsList.value = [
- ...busyData,
- ...ringData,
- ...readyData,
- ...acwData,
- ...heldData,
- ...treeWayData,
- ...unreadyData,
- ...logoutData,
- ];
- };
- onMounted(async () => {
- await nextTick();
- // await getSeatsList();
- // 接收消息
- signalR.SR.on("SeatState", (res: any) => {
- const item = seatsList.value.find((item: any) => item.telNo === res.telNo);
- item.loading = true;
- 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("monitorInfo", (data: any) => {
- console.log("1111", data);
- });
- });
- // 监听和插话消息
- 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>
|