Ver Fonte

feat:新增分机管理页面;

zhangchong há 1 ano atrás
pai
commit
fa79902b78

+ 5 - 1
src/App.vue

@@ -24,6 +24,7 @@ import setIntroduction from '@/utils/setIconfont';
 import { loginPageInfo } from '@/api/login';
 import { getImageUrl } from '@/utils/tools';
 import { useKeepALiveNames } from '@/stores/keepAliveNames';
+import signalR from '@/utils/signalR';
 // 引入组件
 const LockScreen = defineAsyncComponent(() => import('@/layout/lockScreen/index.vue'));
 const Setings = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/setings.vue'));
@@ -99,7 +100,7 @@ onMounted(() => {
 			}
 			// 监听布局配置弹窗点击打开
 			mittBus.on('openSetTingsDrawer', () => {
-        openSetTingsDrawer();
+				openSetTingsDrawer();
 			});
 
 			// 获取缓存中的全屏配置
@@ -118,6 +119,9 @@ onMounted(() => {
 			/*mittBus.on('*', (index, data) => {
       	console.log(index, data);
       });*/
+
+			//  signalR 初始化signalr
+			signalR.init();
 		} catch (error) {
 			console.log(error);
 		}

+ 4 - 9
src/layout/navBars/breadcrumb/index.vue

@@ -1,9 +1,9 @@
 <template>
-	<div class="layout-navbars-breadcrumb-index" :class="{showControl:userInfosConfig.showTelControl}">
+	<div class="layout-navbars-breadcrumb-index" :class="{ showControl: userInfosConfig.showTelControl }">
 		<Logo v-if="setIsShowLogo" />
 		<Breadcrumb />
 		<TelControl v-if="userInfosConfig.showTelControl" />
-		<div v-else style="flex:1"></div>
+		<div v-else style="flex: 1"></div>
 		<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
 		<User />
 	</div>
@@ -17,7 +17,6 @@ import { useRoutesList } from '@/stores/routesList';
 import { useThemeConfig } from '@/stores/themeConfig';
 import { useUserInfo } from '@/stores/userInfo';
 import mittBus from '@/utils/mitt';
-import signalR from '@/utils/signalR';
 
 // 定义接口来定义对象的类型
 interface IndexState {
@@ -41,8 +40,6 @@ const state = reactive<IndexState>({
 	menuList: [],
 });
 
-//  signalR 初始化signalr
-signalR.init();
 
 // 获取用户信息配置
 const userInfosConfig = computed(() => {
@@ -106,12 +103,10 @@ onMounted(() => {
 	mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
 		setFilterRoutes();
 	});
-  // 加入分组
-  signalR.joinGroup('CallCenter');
 });
 // 页面卸载时
 onUnmounted(() => {
-	mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => { });
+	mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
 });
 </script>
 
@@ -128,4 +123,4 @@ onUnmounted(() => {
 	border: none;
 	background: none;
 }
-</style>
+</style>

+ 2 - 0
src/layout/navBars/breadcrumb/telControl.vue

@@ -1517,6 +1517,8 @@ onMounted(async () => {
 	removeTimerOnDuty(); // 移除定时器
 	await getTelsLists(); // 查询所有分机
 	await callCenterConnect(); // 呼叫中心链接
+	// 加入分组
+	signalR.joinGroup('CallCenter');
 });
 </script>
 

+ 8 - 6
src/utils/request.ts

@@ -106,16 +106,18 @@ function httpErrorStatusHandle(error: any) {
 				if (!tokenAbnormal) {
 					tokenAbnormal = true;
 					// 弹出框
-					ElMessageBox.alert('登录过期,请重新登录', '提示', { type: 'warning' })
+					ElMessageBox.alert('登录过期,请重新登录', '提示', { type: 'warning', showClose: false, closeOnClickModal: false, draggable: true })
 						.then(() => {
 							Session.clear(); // 清除浏览器全部临时缓存
 							Local.clear(); // 清除浏览器全部临时缓存
 							Cookie.clear(); // 清除浏览器全部临时缓存
-							router.replace(
-								`/login?redirect=${router.currentRoute.value.path}&params=${JSON.stringify(
-									router.currentRoute.value.query ? router.currentRoute.value.query : router.currentRoute.value.params
-								)}`
-							).then(() => {}); // 去登录页
+							router
+								.replace(
+									`/login?redirect=${router.currentRoute.value.path}&params=${JSON.stringify(
+										router.currentRoute.value.query ? router.currentRoute.value.query : router.currentRoute.value.params
+									)}`
+								)
+								.then(() => {}); // 去登录页
 							location.reload(); //刷新页面
 						})
 						.catch((): void => {});

+ 3 - 3
src/utils/signalR.ts

@@ -71,12 +71,12 @@ export default {
 	 * @returns
 	 */
 	async joinGroup(groupName: string) {
-		if (this.SR.state === 'Connected') {
+		if (this.SR?.state === 'Connected') {
 			// 判断是否已经建立链接 如果没有 先链接
 			await this.SR.invoke('JoinGroupAsync', { GroupName: groupName });
 		} else {
 			// 等待链接成功再发送
-			if (this.SR.state === 'Connecting') {
+			if (this.SR?.state === 'Connecting') {
 				setTimeout(async () => {
 					await this.SR.invoke('JoinGroupAsync', { GroupName: groupName });
 				}, 500);
@@ -85,7 +85,7 @@ export default {
 			await this.start()
 				.then(async () => {
 					setTimeout(async () => {
-						await this.SR.invoke('JoinGroupAsync', { GroupName: groupName });
+						await this.SR?.invoke('JoinGroupAsync', { GroupName: groupName });
 					}, 500);
 				})
 				.catch((err) => {

+ 262 - 0
src/views/tels/extension/index.vue

@@ -0,0 +1,262 @@
+<template>
+	<div class="tels-callLog-container layout-pd">
+		<el-card shadow="never">
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent class="mt15" label-width="100px">
+				<el-row :gutter="10">
+					<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
+						<el-form-item label="通话时间段" prop="crTime">
+							<el-date-picker
+								v-model="state.queryParams.crTime"
+								type="datetimerange"
+								unlink-panels
+								range-separator="至"
+								start-placeholder="开始时间"
+								end-placeholder="结束时间"
+								:shortcuts="shortcuts"
+								@change="queryList"
+								value-format="YYYY-MM-DD[T]HH:mm:ss"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
+						<el-form-item label="主叫" prop="CPN">
+							<el-input v-model="state.queryParams.CPN" placeholder="请输入主叫号码" clearable @keyup.enter="queryList" />
+						</el-form-item>
+					</el-col>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8" v-show="!searchCol">
+							<el-form-item label="被叫" prop="CDPN">
+								<el-input v-model="state.queryParams.CDPN" placeholder="请输入被叫号码" clearable @keyup.enter="queryList" />
+							</el-form-item>
+						</el-col>
+					</transition>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8" v-show="!searchCol">
+							<el-form-item label="电话方向" prop="Direction">
+								<el-select v-model="state.queryParams.Direction" placeholder="请选择电话方向" clearable class="w100">
+									<el-option v-for="item in state.callDirection" :value="item.key" :key="item.key" :label="item.value" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8" v-show="!searchCol">
+							<el-form-item label="通话结果" prop="OnState">
+								<el-select v-model="state.queryParams.OnState" placeholder="请选择通话结果" clearable class="w100">
+									<el-option v-for="item in state.onState" :value="item.key" :key="item.key" :label="item.value" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8" v-show="!searchCol">
+							<el-form-item label="中继号" prop="Gateway">
+								<el-input v-model="state.queryParams.Gateway" placeholder="请输入中继号" clearable @keyup.enter="queryList" />
+							</el-form-item>
+						</el-col>
+					</transition>
+					<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
+						<div class="flex-end w100">
+							<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+							<el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
+								<SvgIcon name="ele-Refresh" class="mr5" />重置
+							</el-button>
+							<el-button link type="primary" @click="closeSearch" :loading="state.loading">
+								{{ searchCol ? '展开' : '收起' }}
+								<SvgIcon :class="{ 'is-reverse': searchCol }" name="ele-ArrowUp" class="mr5 arrow" size="18px" />
+							</el-button>
+						</div>
+					</el-col>
+				</el-row>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<!-- 表格 -->
+			<el-table :data="state.tableList" v-loading="state.loading">
+				<el-table-column prop="cpn" label="主叫" show-overflow-tooltip width="120"></el-table-column>
+				<el-table-column prop="cdpn" label="被叫" show-overflow-tooltip width="120"></el-table-column>
+				<el-table-column prop="ringTimes" label="响铃次数" show-overflow-tooltip width="110"></el-table-column>
+				<el-table-column prop="gateway" label="中继号" show-overflow-tooltip width="120"></el-table-column>
+				<el-table-column prop="duration" label="通话时间(秒)" show-overflow-tooltip width="120"></el-table-column>
+				<el-table-column prop="onStateText" label="通话结果" show-overflow-tooltip> </el-table-column>
+				<el-table-column prop="callDirectionText" label="电话方向" show-overflow-tooltip></el-table-column>
+				<el-table-column prop="endByText" label="挂机类型" show-overflow-tooltip width="120"></el-table-column>
+				<el-table-column label="ivr开始时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.beginIvrTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="ivr结束时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.endIvrTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="队列开始时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.beginQueueTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="队列结束时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.endQueueTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="开始通话时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.createdTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template></el-table-column
+				>
+				<el-table-column label="应答时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.answeredTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="结束通话时间" show-overflow-tooltip width="170">
+					<template #default="{ row }">
+						<span>{{ formatDate(row.overTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
+					</template>
+				</el-table-column>
+				<el-table-column label="操作" width="100" fixed="right" align="center">
+					<template #default="{ row }">
+						<el-button link type="primary" @click="onConnect(row)" title="监听分机" v-auth="'tels:extension:listen'"> 监听分机 </el-button>
+					</template>
+				</el-table-column>
+				<template #empty>
+					<Empty />
+				</template>
+			</el-table>
+			<!-- 分页 -->
+			<pagination
+				:total="state.total"
+				v-model:page="state.queryParams.pageIndex"
+				v-model:limit="state.queryParams.pageSize"
+				@pagination="queryList"
+			/>
+		</el-card>
+		<!-- 播放录音 -->
+		<play-record ref="playRecordRef" />
+		<!-- 业务关联 -->
+		<connect-business ref="connectBusinessRef" @updateList="queryList" />
+	</div>
+</template>
+
+<script lang="ts" setup name="telsExtension">
+import { defineAsyncComponent, onActivated, onDeactivated, onMounted, reactive, ref } from 'vue';
+import type { FormInstance } from 'element-plus';
+import { ElButton, ElMessageBox } from 'element-plus';
+import { downloadFile, throttle } from '@/utils/tools';
+import { callBaseData, callLogPaged } from '@/api/tels/callLog';
+import { formatDate } from '@/utils/formatTime';
+import { shortcuts } from '@/utils/constants';
+import signalR from '@/utils/signalR';
+
+// 引入组件
+const PlayRecord = defineAsyncComponent(() => import('@/views/tels/callLog/component/Play-record.vue')); // 播放录音
+const ConnectBusiness = defineAsyncComponent(() => import('@/views/tels/callLog/component/Connect-business.vue')); // 关联工单还是回访
+
+// 定义变量内容
+const state = reactive(<any>{
+	queryParams: {
+		pageIndex: 1, // 当前页
+		pageSize: 10, // 每页条数
+		StaffNo: null, // 分机号
+		CPN: null, // 中继号码
+		CDPN: null, // 分机号
+		Direction: null, // 呼叫类型
+		OnState: null, // 结果
+		crTime: [],
+	},
+	tableList: [], // 列表数据
+	loading: false, // 加载
+	total: 0, // 总条数
+});
+const ruleFormRef = ref<FormInstance>(); // 表单ref
+const searchCol = ref(true); // 展开/收起
+// 展开/收起
+const closeSearch = () => {
+	searchCol.value = !searchCol.value;
+};
+/** 通话记录列表 */
+const queryList = throttle(async () => {
+	state.loading = true;
+	try {
+		const request = {
+			pageIndex: state.queryParams.pageIndex,
+			pageSize: state.queryParams.pageSize,
+			CPN: state.queryParams.CPN,
+			CDPN: state.queryParams.CDPN,
+			Direction: state.queryParams.Direction,
+			OnState: state.queryParams.OnState,
+			StaffNo: state.queryParams.StaffNo,
+			StartTime: state.queryParams.crTime[0],
+			EndTime: state.queryParams.crTime[1],
+			Gateway: state.queryParams.Gateway,
+		};
+		const response = await callLogPaged(request);
+		state.tableList = response.result?.items ?? [];
+		state.total = response.result?.total ?? 0;
+		state.loading = false;
+	} catch (e) {
+		state.loading = false;
+		console.log(e);
+	}
+}, 300);
+/** 重置按钮操作 */
+const resetQuery = throttle((formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	queryList();
+}, 300);
+// 播放录音
+const playRecordRef = ref<RefType>();
+const onPlaySoundRecording = (val: any) => {
+	playRecordRef.value.openDialog(val.recordingFileUrl);
+};
+// 下载录音
+const onDownload = (row: any) => {
+	ElMessageBox.confirm(`您确定要下载此录音吗?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			downloadFile(row.recordingFileUrl, row.recordingFileName);
+			// window.open(row.recordUrl);
+		})
+		.catch(() => {});
+};
+// 关联业务
+const connectBusinessRef = ref<RefType>();
+const onConnect = (row: any) => {
+	connectBusinessRef.value.openDialog(row);
+};
+// 基础信息
+const getBaseData = async () => {
+	const response = await callBaseData();
+	state.callDirection = response.result.callDirection;
+	state.onState = response.result.onState;
+};
+onMounted(() => {
+	getBaseData();
+	queryList();
+	signalR.joinGroup('callLog');
+});
+onDeactivated(() => {
+	signalR.leaveGroup('callLog');
+});
+onActivated(() => {
+	signalR.joinGroup('callLog');
+});
+</script>
+<style lang="scss" scoped>
+.arrow {
+	transition: transform var(--el-transition-duration);
+	cursor: pointer;
+}
+.arrow.is-reverse {
+	transform: rotateZ(-180deg);
+}
+</style>

+ 20 - 17
src/views/todo/seats/accept/Voice-assistant.vue

@@ -106,7 +106,7 @@
 	</div>
 </template>
 <script setup lang="ts" name="orderAcceptVoiceAssistant">
-import { nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, onMounted, onUnmounted, ref, watch } from 'vue';
+import { nextTick, onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
 import { ElMessageBox } from 'element-plus';
 import { getImageUrl } from '@/utils/tools';
 import axios from 'axios';
@@ -144,19 +144,8 @@ const messageList = ref<any>([
 	},*/
 ]); // 消息列表
 const recognizeList = ref<EmptyObjectType>({}); // 识别信息
-// 设置初始化,防止刷新时恢复默认
-onMounted(() => {
-	// 接受消息
-	mittBus.on('wsReceive', (message: any) => {
-		const data = JSON.parse(message.data);
-		console.log('wsReceive', data, route);
-
-		wsReceive(message);
-	});
-});
 const route = useRoute();
 const talkEnd = ref(false); // 通话结束
-// data.body.content.callId === route.params.callId
 const wsReceive = (message: any) => {
 	try {
 		const data = JSON.parse(message.data);
@@ -231,7 +220,6 @@ const scrollToBottom = async () => {
 	isAutoScroll.value = true;
 };
 watch(messageList.value, (val) => {
-	console.log('messageList', val);
 	scrollToBottom();
 });
 // 继续滚动
@@ -278,12 +266,27 @@ watch(
 		});
 	}
 );
-onDeactivated(() => {
-	console.log('缓存离开');
-	mittBus.off('wsReceive');
+// 订阅消息
+const subscribe = () => {
+	// 接受消息
+	mittBus.on('wsReceive', (message: any) => {
+		const data = JSON.parse(message.data);
+		if (data.body.content.callId === route.params.callId) { // 判断是否是当前通话
+			wsReceive(message);
+		}
+	});
+};
+onMounted(() => {
+	// 进入页面订阅
+	subscribe();
 });
 onActivated(() => {
-	console.log('缓存进入');
+	// 缓存进入重新订阅
+	subscribe();
+});
+onDeactivated(() => {
+	// 缓存离开取消订阅
+	mittBus.off('wsReceive');
 });
 </script>
 

+ 14 - 3
src/views/todo/seats/accept/index.vue

@@ -653,8 +653,20 @@ const getParentId = (val: any, arr: string[]) => {
 const areaRef = ref<RefType>();
 const changeArea = () => {
 	const currentNode = areaRef.value.getCheckedNodes();
-	console.log(currentNode[0], '事发地址');
-	state.ruleForm.address = currentNode[0].pathLabels.join(''); // 事发地址
+	// 判断数组长度
+	if (currentNode[0].pathLabels.length <= 4) {
+		state.ruleForm.province = currentNode[0].pathLabels[0]; // 省
+		state.ruleForm.city = currentNode[0].pathLabels[1] ?? ''; // 市
+		state.ruleForm.county = currentNode[0].pathLabels[2] ?? ''; // 区
+		state.ruleForm.town = currentNode[0].pathLabels[3] ?? ''; // 地区
+	} else {
+		// 如果数组长度大于4
+		state.ruleForm.province = currentNode[0].pathLabels[0]; // 省
+		state.ruleForm.city = currentNode[0].pathLabels[1] ?? ''; // 市
+		state.ruleForm.county = currentNode[0].pathLabels[2] ?? ''; // 区
+		state.ruleForm.town = currentNode[0].pathLabels[3] ?? ''; // 地区
+		// state.ruleForm.areaText = currentNode[0].pathLabels.slice(4).join('') ?? ''; // 地区
+	}
 };
 // 根据热点和事发地址去查询重复性事件 是否展示重复性事件
 const repeatEventRef = ref<RefType>();
@@ -1098,6 +1110,5 @@ onBeforeMount(() => {
 onMounted(async () => {
 	await loadAddress();
 	await loadExtra();
-
 });
 </script>