Browse Source

reactor:部门办件统计表中,增加【部门办件统计明细表】; 部门办件统计板块:新增【部门超期件明细】;电话控件样式调整;关于新增退回列表和退回不通过原因的显示(未完成)

zhangchong 9 months ago
parent
commit
b19180b3fd

+ 1 - 1
.env.development

@@ -31,6 +31,6 @@ VITE_JTHS_API_URL=http://118.121.58.161:19021
 # 捷通华声AppKey
 VITE_JTHS_APPKEY=MTAwMDAx
 # 当前地州市
-VITE_CURRENT_CITY=zigong
+VITE_CURRENT_CITY=yibin
 # 兴唐呼叫中心ws地址
 VITE_XINGTANG_SOCKET_URL=ws://123.56.10.71:7681

+ 12 - 1
src/api/business/return.ts

@@ -4,7 +4,7 @@
  */
 import request from '@/utils/request';
 /**
- * @description 省退回申请列表
+ * @description 工单退回列表
  * @param {object} params
  */
 export const returnList = (params: object) => {
@@ -14,6 +14,17 @@ export const returnList = (params: object) => {
 		params,
 	});
 };
+/**
+ * @description 省退回申请列表
+ * @param {object} params
+ */
+export const provinceReturnList = (params: object) => {
+	return request({
+		url: `/api/v1/Order/send_back`,
+		method: 'get',
+		params,
+	});
+};
 /**
  * @description 省退回申请新增
  * @param {object} data

+ 22 - 0
src/api/public/wex.ts

@@ -84,6 +84,28 @@ export const getTelList = (params?: object) => {
 		params,
 	});
 };
+/**
+ * @description 查询呼叫中心分机列表
+ * @param params
+ */
+export const getTelListV2 = (params?: object) => {
+	return request({
+		url: `/api/v1/Tel`,
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 查询呼叫中心分机组列表
+ * @param params
+ */
+export const getTelGroupList = (params?: object) => {
+	return request({
+		url: `/api/v1/Tel/groups`,
+		method: 'get',
+		params,
+	});
+};
 /**
  * @description 查询呼叫中心黑白名单列表
  * @param params

+ 62 - 0
src/api/statistics/department.ts

@@ -104,6 +104,43 @@ export const departmentOverdueDetailExport = (data: object) => {
     reduce_data_format: false
   });
 }
+/**
+ * @description 部门超期列表基础数据
+ * @param {object} params
+ */
+export const departmentOverdueBase = (params?: object) => {
+  return request({
+    url: `/api/v1/BiOrder/org_data_list_detail_all/base-data`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 部门超期列表明细
+ * @param {object} params
+ */
+export const departmentOverdueList = (params?: object) => {
+  return request({
+    url: `/api/v1/BiOrder/org_data_list_detail_all`,
+    method: 'get',
+    params,
+    paramsSerializer: (params:any) => qs.stringify(params)
+  });
+}
+/**
+ * @description 部门超期列表明细导出
+ * @param {object} data
+ */
+export const departmentOverdueListExport = (data: object) => {
+  return request({
+    url: `/api/v1/BiOrder/org_data_list_detail_all/_export`,
+    method: 'post',
+    data,
+    responseType: 'blob'
+  }, {
+    reduce_data_format: false
+  });
+}
 /**
  * @description 部门满意度统计
  * @param {object} params
@@ -255,6 +292,31 @@ export const departmentOrderDetailExport = (data: object) => {
     reduce_data_format: false
   });
 }
+/**
+ * @description 部门办件列表明细
+ * @param {object} params
+ */
+export const departmentOrderList = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/departmental_processing_statistics_details_list`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 部门办件列表明细导出
+ * @param {object} data
+ */
+export const departmentOrderListExport = (data: object) => {
+  return request({
+    url: `/api/v1/BiOrder/departmental_processing_statistics_details_list_export`,
+    method: 'post',
+    data,
+    responseType: 'blob'
+  }, {
+    reduce_data_format: false
+  });
+}
 /**
  * @description 部门办件统计表子级部门
  * @param {object} params

+ 2 - 1
src/components/AuditRecord/index.vue

@@ -30,6 +30,7 @@ const state = reactive<any>({
 });
 // 打开弹窗
 const openDialog = async (row: any) => {
+  state.dialogVisible = true;
   try {
     state.loading = true;
     // 查询审核记录
@@ -37,10 +38,10 @@ const openDialog = async (row: any) => {
     state.traces = res.result?.traces ?? [];
     state.traces = formatTraces(state.traces);
     state.dialogTitle = row.dialogTitle ?? '';
-    state.dialogVisible = true;
     state.loading = false;
   } catch (error) {
     state.loading = false;
+    state.dialogVisible = false;
   }
 };
 const formatTraces = (val: any) => {

+ 9 - 0
src/components/OrderDetail/index.vue

@@ -230,6 +230,12 @@
 							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="state.ruleForm.sendBackOpinion">
 								<el-form-item label="退回意见"> {{ state.ruleForm.sendBackOpinion }} </el-form-item>
 							</el-col>
+              <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="state.ruleForm.sendBackRefuseOpinion">
+                <el-form-item label="退回不通过原因"> {{ state.ruleForm.sendBackRefuseOpinion }} </el-form-item>
+              </el-col>
+              <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="state.ruleForm.sendBackAuditTime">
+                <el-form-item label="退回审批时间"> {{ formatDate(item.sendBackAuditTime, 'YYYY-mm-dd HH:MM:SS') }} </el-form-item>
+              </el-col>
 							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="state.ruleForm.orderRemarks?.length">
 								<el-form-item label="备注信息">
 									<el-row class="w100">
@@ -938,4 +944,7 @@ defineExpose({
 :deep(.el-tabs__item) {
 	font-size: var(--el-font-size-base);
 }
+:deep(.el-collapse-item__wrap){
+  border-bottom: none;
+}
 </style>

+ 1 - 4
src/layout/component/aside.vue

@@ -5,7 +5,6 @@
 			<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
 				<Vertical :menuList="state.menuList" />
 			</el-scrollbar>
-			<!--      <LayoutFooter />-->
 		</el-aside>
 	</div>
 </template>
@@ -22,9 +21,7 @@ import { getCurrentCityConfig } from '@/utils/appConfig';
 // 引入组件
 const Logo = defineAsyncComponent(() => import('@/layout/logo/index.vue'));
 const Vertical = defineAsyncComponent(() => import('@/layout/navMenu/vertical.vue'));
-const LayoutFooter = defineAsyncComponent(() => import('@/layout/footer/footer.vue'));
-// const { isShowLogo } = getCurrentCityConfig();
-const isShowLogo = ref(false);
+const { isShowLogo } = getCurrentCityConfig();
 // 定义变量内容
 const stores = useRoutesList();
 const storesThemeConfig = useThemeConfig();

+ 2 - 2
src/layout/component/main.vue

@@ -49,9 +49,9 @@ const setMainHeight = computed(() => {
 	const { isTagsview, layout } = themeConfig.value;
 	const { showTelControl } = userInfos.value;
 	if (isTagsview && layout !== 'classic' && showTelControl) {
-		return '140px';
+		return '85px';
 	} else if (isTagsview && layout !== 'classic' && !showTelControl) {
-		return '120px';
+		return '85px';
 	} else {
 		return '51px';
 	}

+ 13 - 17
src/layout/navBars/breadcrumb/index.vue

@@ -1,18 +1,20 @@
 <template>
-	<div class="layout-navbars-breadcrumb-index" :class="{ showControl: userInfosConfig.showTelControl }">
+	<div
+		class="layout-navbars-breadcrumb-index"
+	>
 		<Logo v-if="setIsShowLogo" />
 		<Breadcrumb />
-    <template v-if="userInfosConfig.showTelControl" >
-      <component :is="currentCity" />
-    </template>
-    <div v-else style="flex: 1"></div>
+		<template v-if="userInfosConfig.showTelControl">
+			<component :is="currentCity" />
+		</template>
+		<div v-else style="flex: 1"></div>
 		<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
 		<User />
 	</div>
 </template>
 
 <script setup lang="ts" name="layoutBreadcrumbIndex">
-import { computed, reactive, onMounted, onBeforeUnmount, defineAsyncComponent } from 'vue';
+import { computed, reactive, onMounted, onBeforeUnmount, defineAsyncComponent, ref } from 'vue';
 import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useRoutesList } from '@/stores/routesList';
@@ -34,12 +36,12 @@ const yibin = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/ybT
 const zigong = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/zgTel.vue')); // 自贡呼叫中心(兴唐)
 
 const COMPONENT_LIST = {
-  'yibin': yibin,
-  'zigong': zigong,
+	yibin: yibin,
+	zigong: zigong,
 };
 // 当前地州市
 const currentCity = computed(() => {
-  return COMPONENT_LIST[import.meta.env.VITE_CURRENT_CITY];
+	return COMPONENT_LIST[import.meta.env.VITE_CURRENT_CITY];
 });
 // 定义变量内容
 const stores = useRoutesList();
@@ -53,7 +55,6 @@ const state = reactive<IndexState>({
 	menuList: [],
 });
 
-
 // 获取用户信息配置
 const userInfosConfig = computed(() => {
 	return userInfos.value;
@@ -125,15 +126,10 @@ onBeforeUnmount(() => {
 
 <style scoped lang="scss">
 .layout-navbars-breadcrumb-index {
-	height: 80px;
+  height: 50px;
 	display: flex;
 	align-items: center;
 	background: var(--hotline-bg-topBar);
 	border-bottom: 1px solid var(--hotline-border-color-light);
 }
-
-.showControl {
-	border: none;
-	background: none;
-}
-</style>
+</style>

+ 197 - 150
src/layout/navBars/breadcrumb/ybTel.vue

@@ -1,15 +1,47 @@
 <template>
 	<div class="phoneControls" v-loading="state.loading">
-		<!-- 电话状态 -->
-		<div class="infos">
-			<div class="pt5" :class="talkTime ? '' : 'mt8'"><span>分机号:</span>{{ telStatusInfo.telsNo }}</div>
-			<div class="pt5" :class="talkTime ? '' : 'mt8'">
-				<span>状态:</span><b class="dutyOn_status">{{ currentStatusText }}</b>
-			</div>
-			<div class="pt5" v-if="talkTime">
-				<span>通话时长:</span> <el-text tag="b" type="danger">{{ formatDuration(talkTime, false) }}</el-text>
-			</div>
-		</div>
+		<el-popover placement="top-start" :width="240" trigger="hover" v-model:visible="showPop">
+			<template #reference>
+				<div class="status-box">
+					<span class="color-primary">{{ currentStatusText }}</span>
+					<el-text tag="b" v-if="currentStatusText === '通话中'">{{ formatDuration(talkTime) }}</el-text>
+					<el-text tag="b" v-else-if="['空闲', '外呼中'].includes(currentStatusText)">{{ formatDuration(idleTime) }}</el-text>
+					<el-text tag="b" v-else-if="currentStatusText === '小休中'">{{ formatDuration(busyTime) }}</el-text>
+					<el-text tag="b" v-else-if="currentStatusText === '整理中'">{{ formatDuration(arrangeTime) }}</el-text>
+					<SvgIcon name="ele-CaretBottom" class="arrow" :class="{'is-reverse':showPop}"/>
+				</div>
+			</template>
+			<template #default>
+				<template v-if="telStatusInfo.isDutyOn">
+					<div class="flex-center-between" v-if="telStatusInfo.telsNo">
+						<span class="ml10">分机号</span>
+						<el-text tag="b">{{ telStatusInfo.telsNo }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="signTime">
+						<span class="ml10">签入时长</span>
+						<el-text tag="b">{{ formatDuration(signTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="talkTime && currentStatusText !== '通话中'">
+						<span class="ml10">通话时长</span>
+						<el-text tag="b">{{ formatDuration(talkTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="idleTime && !['空闲', '外呼中'].includes(currentStatusText)">
+						<span class="ml10">空闲时长</span>
+						<el-text tag="b">{{ formatDuration(idleTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="busyTime && currentStatusText !== '小休中'">
+						<span class="ml10">示忙时长</span>
+						<el-text tag="b">{{ formatDuration(busyTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="arrangeTime && currentStatusText !== '整理中'">
+						<span class="ml10">整理时长</span>
+						<el-text tag="b">{{ formatDuration(arrangeTime) }}</el-text>
+					</div>
+				</template>
+				<template v-else> <span class="color-info">请签入</span> </template>
+			</template>
+		</el-popover>
+
 		<!--  按钮列表  -->
 		<div class="btn-container">
 			<!-- 签入 -->
@@ -19,8 +51,6 @@
 					class="item active"
 					@click="onControlClick('dutyOff')"
 					v-if="activeArr.includes('dutyOff')"
-					@mouseenter="onHover('dutyOffSrc', 'phoneControls/dutyOff_white.png')"
-					@mouseleave="onHover('dutyOffSrc', 'phoneControls/dutyOff_blue.png')"
 					title="签出"
 				>
 					<img :src="state.dutyOffSrc" alt="" />
@@ -34,13 +64,7 @@
 			</template>
 			<!-- 灰色签入不可用 -->
 			<template v-else>
-				<div
-					class="item active"
-					@click="onControlClick('dutyOn')"
-					title="签入"
-					@mouseenter="onHover('dutyOnSrc', 'phoneControls/dutyOn_white.png')"
-					@mouseleave="onHover('dutyOnSrc', 'phoneControls/dutyOn_blue.png')"
-				>
+				<div class="item active" @click="onControlClick('dutyOn')" title="签入">
 					<img :src="state.dutyOnSrc" alt="" />
 					<span>签入</span>
 				</div>
@@ -50,13 +74,11 @@
 			<template v-if="telStatusInfo.isDutyOn && activeArr.includes('callOut')">
 				<div
 					class="item active"
-					:title="telStatusInfo.isHold ? '取消外呼模式' : '外呼模式'"
+					:title="telStatusInfo.isHold ? '取消外呼' : '外呼模式'"
 					@click="onControlClick(telStatusInfo.isCallOut ? 'unCallOut' : 'callOut')"
-					@mouseenter="onHover('callOutSrc', 'phoneControls/callOut_white.png')"
-					@mouseleave="onHover('callOutSrc', 'phoneControls/callOut_blue.png')"
 				>
 					<img :src="state.callOutSrc" alt="" />
-					<span>{{ telStatusInfo.isCallOut ? '取消外呼模式' : '外呼模式' }}</span>
+					<span>{{ telStatusInfo.isCallOut ? '取消外呼' : '外呼模式' }}</span>
 				</div>
 			</template>
 			<!-- 灰色外呼不可用 -->
@@ -74,8 +96,6 @@
 					:class="state.active.includes('hangup') ? 'active' : ''"
 					@click="onControlClick('hangup')"
 					title="挂断"
-					@mouseenter="onHover('hangupSrc', 'phoneControls/hangup_white.png')"
-					@mouseleave="onHover('hangupSrc', 'phoneControls/hangup_blue.png')"
 				>
 					<img :src="state.hangupSrc" alt="" />
 					<span>挂断</span>
@@ -96,8 +116,6 @@
 					@click="onControlClick('restEnd')"
 					v-if="telStatusInfo.isRest === 'resting'"
 					title="结束小休"
-					@mouseenter="onHover('restSrc', 'phoneControls/rest_white.png')"
-					@mouseleave="onHover('restSrc', 'phoneControls/rest_blue.png')"
 				>
 					<img :src="state.restSrc" alt="" />
 					<span
@@ -109,8 +127,6 @@
 					@click="onControlClick('rest')"
 					v-else-if="telStatusInfo.isRest === 'unRest'"
 					title="小休"
-					@mouseenter="onHover('restSrc', 'phoneControls/rest_white.png')"
-					@mouseleave="onHover('restSrc', 'phoneControls/rest_blue.png')"
 				>
 					<img :src="state.restSrc" alt="" />
 					<span>小休 </span>
@@ -134,8 +150,6 @@
 					class="item active"
 					:title="telStatusInfo.isHold ? '取消保持' : '保持'"
 					@click="onControlClick(telStatusInfo.isHold ? 'unHold' : 'hold')"
-					@mouseenter="onHover('holdSrc', 'phoneControls/hold_white.png')"
-					@mouseleave="onHover('holdSrc', 'phoneControls/hold_blue.png')"
 				>
 					<img :src="state.holdSrc" alt="" />
 					<span>{{ telStatusInfo.isHold ? '取消保持' : '保持' }}</span>
@@ -155,8 +169,6 @@
 					class="item active"
 					:title="telStatusInfo.isMute ? '取消静音' : '静音'"
 					@click="onControlClick(telStatusInfo.isMute ? 'unMute' : 'mute')"
-					@mouseenter="onHover('muteSrc', 'phoneControls/mute_white.png')"
-					@mouseleave="onHover('muteSrc', 'phoneControls/mute_blue.png')"
 				>
 					<img :src="state.muteSrc" alt="" />
 					<span>{{ telStatusInfo.isMute ? '取消静音' : '静音' }}</span>
@@ -175,12 +187,10 @@
 				<div
 					class="item active"
 					@click="onControlClick(telStatusInfo.isTalkingDeal ? 'unTalkingDeal' : 'TalkingDeal')"
-					@mouseenter="onHover('talkingDealSrc', 'phoneControls/talkingDeal_white.png')"
-					:title="telStatusInfo.isTalkingDeal ? '取消话后整理' : '话后整理'"
-					@mouseleave="onHover('talkingDealSrc', 'phoneControls/talkingDeal_blue.png')"
+					:title="telStatusInfo.isTalkingDeal ? '取消整理' : '话后整理'"
 				>
 					<img :src="state.talkingDealSrc" alt="" />
-					<span>{{ telStatusInfo.isTalkingDeal ? '取消话后整理' : '话后整理' }}</span>
+					<span>{{ telStatusInfo.isTalkingDeal ? '取消整理' : '话后整理' }}</span>
 				</div>
 			</template>
 			<!-- 话后整理中不可用 -->
@@ -197,8 +207,6 @@
 					class="item active"
 					@click="onControlClick('transfer')"
 					title="保持"
-					@mouseenter="onHover('transferSrc', 'phoneControls/transfer_white.png')"
-					@mouseleave="onHover('transferSrc', 'phoneControls/transfer_blue.png')"
 				>
 					<img :src="state.transferSrc" alt="" />
 					<span>转接</span>
@@ -216,9 +224,7 @@
 			<template v-if="telStatusInfo.isDutyOn && activeArr.includes('conference') && onCallArr.length !== 1">
 				<div
 					class="item active"
-					@mouseenter="onHover('conferenceSrc', 'phoneControls/conference_white.png')"
 					title="三方会议"
-					@mouseleave="onHover('conferenceSrc', 'phoneControls/conference_blue.png')"
 					@click="onControlClick('conference')"
 				>
 					<img :src="state.conferenceSrc" alt="" />
@@ -231,9 +237,7 @@
 					<template #reference>
 						<div
 							class="item active"
-							@mouseenter="onHover('conferenceSrc', 'phoneControls/conference_white.png')"
 							title="三方会议"
-							@mouseleave="onHover('conferenceSrc', 'phoneControls/conference_blue.png')"
 						>
 							<img :src="state.conferenceSrc" alt="" />
 							<span>三方会议({{ onCallArr.length }})</span>
@@ -259,9 +263,7 @@
 			<template v-if="telStatusInfo.isDutyOn && activeArr.includes('outbound')">
 				<div
 					class="item active"
-					@mouseenter="onHover('outboundSrc', 'phoneControls/outbound_white.png')"
 					title="呼叫"
-					@mouseleave="onHover('outboundSrc', 'phoneControls/outbound_blue.png')"
 					@click="onControlClick('outbound')"
 				>
 					<img :src="state.outboundSrc" alt="" />
@@ -276,11 +278,6 @@
 				</div>
 			</template>
 		</div>
-		<!-- 签入时长 -->
-		<div class="duty-on-time">
-			<span class="duty-on-time-label">签入时长</span>
-			<el-text class="duty-on-time-time" tag="b" type="danger" v-if="onDutyTime">{{ formatDuration(onDutyTime) }}</el-text>
-		</div>
 		<!-- 等待人数 -->
 		<div class="wait-box">
 			<div class="today-wait">
@@ -574,6 +571,7 @@ import { getDataByCode } from '@/api/system/dict';
 import { useTransition, useDocumentVisibility } from '@vueuse/core';
 import { useWebSocket } from '@/hooks/useWebsocket';
 import { olaFn, olaWs } from '@/utils/olaFn';
+import { useIntervalFn } from '@vueuse/shared/index';
 // 引入组件
 const CommonAdvice = defineAsyncComponent(() => import('@/components/CommonAdvice/index.vue')); // 常用意见
 const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue'));
@@ -644,53 +642,116 @@ const appConfigStore = useAppConfig();
 const { AppConfigInfo } = storeToRefs(appConfigStore); // 系统配置信息
 const storesUserInfo = useUserInfo();
 const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
+const showPop = ref(false);
 
-const talkTime = ref<any>(0); // 通话时长
-const talkTimer = ref<any>(null); // 通话时长定时器
+const talkTime = ref(0); // 通话时长
+const talkTimer = useIntervalFn(
+	() => {
+		talkTime.value += 1;
+		Local.set('talkTime', String(talkTime.value));
+	},
+	1000,
+	{ immediate: false }
+);
 // 开始通话计时
-const startTime = () => {
+const startTalkTimer = () => {
 	let localTalkTime = Local.get('talkTime');
 	if (talkTime.value) {
 		talkTime.value = Number(localTalkTime);
-		talkTimer.value = setInterval(() => {
-			talkTime.value++;
-			Local.set('talkTime', String(talkTime.value));
-		}, 1000);
-	} else {
-		talkTimer.value = setInterval(() => {
-			talkTime.value++;
-			Local.set('talkTime', String(talkTime.value));
-		}, 1000);
 	}
+	talkTimer.resume();
+	stopIdleTime();
 };
-
 // 结束通话计时
-const removeTimer = () => {
+const stopTalkTimer = () => {
+	talkTimer.pause();
 	talkTime.value = 0;
 	Local.remove('talkTime');
-	clearInterval(talkTimer.value);
 };
-// 开始签入时长
-const onDutyTime = ref<any>(0); // 签入时长
-const onDutyTimer = ref<any>(null); // 签入时长定时器
-const startDutyTimer = (second: any) => {
+
+const signTime = ref(0); //签入时长
+// 使用 useInterval 创建定时器
+const signInTimer = useIntervalFn(
+	() => {
+		signTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 签入时长及时开始
+const startSignTime = (second?: number) => {
 	if (second) {
 		// 从后台获取签入时长
 		if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
-		onDutyTime.value = second;
-		onDutyTimer.value = setInterval(() => {
-			onDutyTime.value++;
-		}, 1000);
+		signTime.value = second;
+		signInTimer.resume();
 	} else {
-		onDutyTimer.value = setInterval(() => {
-			onDutyTime.value++;
-		}, 1000);
+		signInTimer.resume();
 	}
 };
-// 结束签入时长
-const removeTimerOnDuty = () => {
-	onDutyTime.value = 0;
-	clearInterval(onDutyTimer.value);
+//  签入时长计时结束
+const stopSignTime = () => {
+	signTime.value = 0;
+	signInTimer.pause();
+};
+
+// 空闲时长
+const idleTime = ref(0);
+const idleTimer = useIntervalFn(
+	() => {
+		idleTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 空闲时长开始
+const startIdleTime = () => {
+	idleTimer.resume();
+	stopBusyTime();
+};
+//  空闲时长计时结束
+const stopIdleTime = () => {
+	idleTime.value = 0;
+	idleTimer.pause();
+};
+// 示忙时长
+const busyTime = ref(0);
+const busyTimer = useIntervalFn(
+	() => {
+		busyTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 示忙时长开始
+const startBusyTime = () => {
+	busyTimer.resume();
+	stopIdleTime();
+};
+//  示忙时长计时结束
+const stopBusyTime = () => {
+	busyTime.value = 0;
+	busyTimer.pause();
+};
+
+// 整理时长
+const arrangeTime = ref(0);
+const arrangeTimer = useIntervalFn(
+	() => {
+		arrangeTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 整理时长开始
+const startArrangeTime = () => {
+	arrangeTimer.resume();
+	stopIdleTime();
+};
+//  示忙时长计时结束
+const stopArrangeTime = () => {
+	arrangeTime.value = 0;
+	arrangeTimer.pause();
 };
 // 监听消息
 const signalRStart = async () => {
@@ -771,10 +832,6 @@ const getThreeWayAndTransfer = async () => {
 		console.log(err, getNowDateTime());
 	}
 };
-// 鼠标移入移出改变图标
-const onHover = (val: string, path: string) => {
-	state[val] = getImageUrl(path);
-};
 // 点击事件
 const onControlClick = (val: string) => {
 	switch (val) {
@@ -945,7 +1002,7 @@ const onMessage = async (event: any) => {
 			if (currentTel.value.telModel === 2) {
 				// 外呼模式
 				// 结束计时
-				removeTimer();
+				stopTalkTimer();
 				// 设置分机号和坐席组
 				useTelStatusStore.setCallInfo({ telsNo: data.agent_extn });
 				// 设置签入状态
@@ -957,10 +1014,11 @@ const onMessage = async (event: any) => {
 				useTelStatusStore.setPhoneControlState(TelStates.onCallOut);
 				state.loading = false;
 				console.log(getNowDateTime() + ':' + '呼叫中心 外呼模式:示闲中');
+				startIdleTime();
 				sendMsg('ready');
 			} else {
 				// 结束计时
-				removeTimer();
+				stopTalkTimer();
 				// 设置分机号和坐席组
 				useTelStatusStore.setCallInfo({ telsNo: data.agent_extn });
 				// 设置签入状态
@@ -977,11 +1035,12 @@ const onMessage = async (event: any) => {
 				}
 				console.log('呼叫中心:示闲中', getNowDateTime());
 				sendMsg('ready');
+				startIdleTime();
 			}
 		} else if (data.state == 'unready') {
 			break_reason(data.private_data);
 			sendMsg('unready'); // 发送消息 业务系统消息通知
-
+			startBusyTime(); // 开始示忙
 			console.log('呼叫中心:示忙中,小休开始', getNowDateTime());
 			// 示忙中
 			useTelStatusStore.setPhoneControlState(TelStates.rest);
@@ -1053,11 +1112,13 @@ const onMessage = async (event: any) => {
 						console.log('呼叫中心:调用示闲', getNowDateTime());
 						onEndAcw(); // 挂机后整理结束
 						clearTimeout(talkDealTimer.value); // 清除话后整理定时器
+						stopArrangeTime();
 					}, time);
 					console.log('呼叫中心:话后整理中', getNowDateTime());
 					// 如果不是话后整理中
 					await onBeginAcw(); // 挂机后整理开始
 					sendMsg('acw');
+					startArrangeTime();
 				} else {
 					// 呼出直接调用示闲
 					olaRef.value.go_ready(); // 示闲
@@ -1122,7 +1183,7 @@ const onMessage = async (event: any) => {
 				} else if (data.private_data == 'three_way') {
 					// 三方来电通话中
 					// 开始计时
-					startTime();
+					startTalkTimer();
 					// 设置电话状态 三方通话中
 					useTelStatusStore.setPhoneControlState(TelStates.onConference);
 					console.log('呼叫中心:三方来电通话中', getNowDateTime());
@@ -1164,7 +1225,7 @@ const onMessage = async (event: any) => {
 						} else if (data.other_answered == true) {
 							// 通话中
 							// 开始计时
-							startTime();
+							startTalkTimer();
 							// 设置电话状态 通话中
 							useTelStatusStore.setPhoneControlState(TelStates.onCall);
 
@@ -1204,7 +1265,7 @@ const onMessage = async (event: any) => {
 						});
 					} else if (data.private_data == 'answered') {
 						// 开始计时
-						startTime();
+						startTalkTimer();
 						// 设置电话状态 通话中
 						useTelStatusStore.setPhoneControlState(TelStates.onCall);
 						console.log('呼叫中心:呼入通话中', getNowDateTime());
@@ -1219,7 +1280,7 @@ const onMessage = async (event: any) => {
 			useTelStatusStore.setCallInfo({ telsNo: data.agent_extn });
 
 			// 结束计时
-			removeTimer();
+			stopTalkTimer();
 			onCallArr.value = [];
 			console.log('呼叫中心:已挂机', onCallArr.value, data, getNowDateTime());
 		}
@@ -1311,7 +1372,7 @@ const onDutyFn = async () => {
 						currentTel.value.queueCallOut = res.result.queueCallOut; // 呼出分机组
 						// 不需要选择分机号和密码 直接签入 传入默认分机号
 						websocket_connect(); //开启消息监听
-						startDutyTimer(res.result.second); // 开启计时 签入时长
+						startSignTime(res.result.second); // 开启计时 签入时长
 						isRest.value = res.result.isRest;
 						state.loading = false;
 					})
@@ -1379,7 +1440,7 @@ const clickOnDuty = (formEl: FormInstance | undefined) => {
 					currentTel.value.queueCallOut = res.result.queueCallOut; // 呼出分机组
 				}
 				websocket_connect(); //开启消息监听
-				startDutyTimer(res.result.second); // 开启计时 签入时长
+				startSignTime(res.result.second); // 开启计时 签入时长
 				isRest.value = res.result.isRest;
 				state.loading = false;
 				state.dutyDialogVisible = false;
@@ -1991,12 +2052,9 @@ const seatAssistOff = () => {
 // 重置状态和清除定时器
 const resetState = () => {
 	useTelStatusStore.resetState();
-	removeTimerOnDuty(); // 移除签入时长定时器
-	removeTimer(); // 移除通话计时器
+	stopSignTime(); // 移除签入时长定时器
+	stopTalkTimer(); // 移除通话计时器
 	clearTimeout(talkDealTimer.value); // 清除话后整理定时器
-	clearInterval(pingTimer.value); // 清除心跳定时器
-	clearInterval(onDutyTimer.value); // 清除签入时长定时器
-	clearInterval(talkTimer.value); // 清除通话时长定时器
 	state.loading = false;
 };
 // 获取当前分机状态
@@ -2011,7 +2069,7 @@ const getTelStatusFn = async () => {
 		currentTel.value.queue = result.queueId; // 呼入分机组
 		currentTel.value.telModel = result.telModel; // 1:普通模式 2:呼出模式
 		currentTel.value.queueCallOut = result.queueCallOut; // 呼出分机组
-		startDutyTimer(result.second); // 开启计时 签入时长
+		startSignTime(result.second); // 开启计时 签入时长
 		isRest.value = result.isRest;
 		isCallHold.value = result.isCallHold;
 		isAcw.value = result.isCallEndArrange; // 是否话后整理中
@@ -2035,7 +2093,7 @@ const callCenterConnect = async () => {
 			currentTel.value.telModel = result.telModel; // 1:普通模式 2:呼出模式
 			currentTel.value.queueCallOut = result.queueCallOut; // 呼出分机组
 			websocket_connect(); //开启消息监听
-			startDutyTimer(result.second); // 开启计时 签入时长
+			startSignTime(result.second); // 开启计时 签入时长
 			isCallHold.value = result.isCallHold;
 			isRest.value = result.isRest;
 			isAcw.value = result.isCallEndArrange; // 是否话后整理中
@@ -2126,62 +2184,57 @@ onBeforeUnmount(() => {
 	display: flex;
 	flex: 1;
 	background-color: var(--el-color-white) !important;
-	box-shadow: 0 1px 8px 0 rgba(0, 15, 49, 0.1);
-	border-bottom-left-radius: 90px;
-	border-bottom-right-radius: 90px;
-	padding: 0 18px 0 40px;
 	color: var(--hotline-color-text-main);
 	height: 100%;
-	.duty-on-time {
-		width: 100px;
-		margin-left: 10px;
-		&-label {
-			display: block;
-			margin-top: 13px;
-		}
-		&-time {
-			display: block;
-			margin-top: 15px;
-		}
-	}
-	.infos {
+	align-items: center;
+	.status-box {
+		width: 240px;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background: var(--el-color-info-light-7);
+		position: relative;
+		box-sizing: border-box;
+		cursor: pointer;
 		text-align: left;
-		width: 150px;
-
-		.dutyOn_status {
-			color: var(--el-color-primary);
-			font-weight: normal;
+		font-size: 14px;
+		padding: 4px 26px 4px 12px;
+		gap: 6px;
+		min-height: 32px;
+		line-height: 24px;
+		border-radius: var(--el-border-radius-round);
+		background-color: var(--el-color-info-light-8);
+		transition: var(--el-transition-duration);
+    margin-right: 10px;
+		.arrow {
+			position: absolute;
+			right: 10px;
+			transition: transform var(--el-transition-duration);
 		}
-		span {
-			display: inline-block;
-			width: 80px;
-			text-align: right;
+		.is-reverse {
+			transform: rotate(180deg);
 		}
 	}
-
 	// 按钮列表
 	.btn-container {
 		display: flex;
-		width: calc(100% - 120px);
 		justify-content: space-between;
-
+		width: 100%;
+    height: 100%;
+		border-right: 1px solid var(--el-border-color);
+    border-left: 1px solid var(--el-border-color);
 		.item {
 			text-align: center;
 			cursor: pointer;
 			width: 100%;
 			user-select: none;
-			height: calc(100% + 20px);
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			//border-right: 1px solid var(--el-border-color);
 			img {
-				display: block;
-				margin: 0 auto;
-				padding-top: 10px;
-			}
-
-			span {
-				margin-top: 5px;
-				display: inline-block;
+				scale: 0.6;
 			}
-
 			&.disabled {
 				cursor: not-allowed;
 				overflow: hidden;
@@ -2191,24 +2244,18 @@ onBeforeUnmount(() => {
 		.active {
 			&:hover {
 				color: var(--hotline-color-white);
-				background-image: url('@/assets/images/phoneControls/active.png');
-				background-repeat: no-repeat;
-				background-size: 100% 100%;
+				background-color: var(--el-color-primary-light-3);
 			}
 		}
 	}
 	.wait-box {
-		width: 120px;
+		width: 170px;
 		display: flex;
 		flex-direction: column;
 		justify-content: center;
-		.today-wait {
-			margin-bottom: 10px;
-		}
-		.today-wait-num,
-		.current-wait-time {
-			font-size: var(--el-font-size-medium);
-		}
+		text-align: center;
+		border-right: 1px solid var(--el-border-color);
+    height: 100%;
 	}
 }
 </style>

+ 306 - 79
src/layout/navBars/breadcrumb/zgTel.vue

@@ -1,26 +1,62 @@
 <template>
 	<div class="phoneControls" v-loading="state.loading">
-		<span class="mr10">当前状态:{{ currentState }}</span>
+		<el-popover :width="240" trigger="hover" v-model:visible="showPop">
+			<template #reference>
+				<div class="mr20 status-box">
+					<span class="color-primary">{{ currentState }}</span>
+					<el-text tag="b" v-if="currentState === '通话中'">{{ formatDuration(talkTime) }}</el-text>
+					<el-text tag="b" v-else-if="currentState === '空闲'">{{ formatDuration(idleTime) }}</el-text>
+					<el-text tag="b" v-else-if="currentState === '示忙中'">{{ formatDuration(busyTime) }}</el-text>
+					<SvgIcon name="ele-CaretBottom" class="arrow" :class="showPop ? 'is-reverse' : ''" />
+				</div>
+			</template>
+			<template #default>
+				<template v-if="m_bLogin">
+					<div class="flex-center-between" v-if="m_strUserNo">
+						<span class="ml10">分机号</span>
+						<el-text tag="b">{{ m_strUserNo }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="signTime">
+						<span class="ml10">签入时长</span>
+						<el-text tag="b">{{ formatDuration(signTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="talkTime && currentState !== '通话中'">
+						<span class="ml10">通话时长</span>
+						<el-text tag="b">{{ formatDuration(talkTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="idleTime && currentState !== '空闲'">
+						<span class="ml10">空闲时长</span>
+						<el-text tag="b">{{ formatDuration(idleTime) }}</el-text>
+					</div>
+					<div class="flex-center-between mt10" v-if="busyTime && currentState !== '示忙中'">
+						<span class="ml10">示忙时长</span>
+						<el-text tag="b">{{ formatDuration(busyTime) }}</el-text>
+					</div>
+				</template>
+				<template v-else> <span class="color-info flex flex-center-center">请签入</span> </template>
+			</template>
+		</el-popover>
+
 		<el-button type="primary" @click="onEvent('signOut')" v-if="m_bLogin">签出</el-button>
 		<el-button type="primary" @click="onEvent('signIn')" v-else>签入</el-button>
-		<el-button type="primary" @click="onEvent('busy')" v-if="!m_bTelBusy && m_bCallConnect">示忙</el-button>
-		<el-button type="primary" @click="onEvent('idle')" v-if="m_bTelBusy && m_bCallConnect">示闲</el-button>
-		<el-button type="primary" @click="onEvent('hangup')" v-if="m_bCallConnect || m_isRing">挂机</el-button>
-		<el-button type="primary" @click="onEvent('callOut')" v-if="m_bLogin && !m_isRing">外呼</el-button>
-		<el-button type="primary" @click="onEvent('hold')" v-if="!m_IsHold && m_bCallConnect">保持</el-button>
-		<el-button type="primary" @click="onEvent('reHold')" v-if="m_IsHold && m_bCallConnect">恢复</el-button>
-		<el-button type="primary" @click="onEvent('consult')" v-if="m_bCallConnect">咨询</el-button>
-		<el-button type="primary" @click="onEvent('transfer')" v-if="m_bCallConnect">转接</el-button>
-		<el-button type="primary" @click="onEvent('transferMz')" v-if="m_bCallConnect">盲转</el-button>
-		<el-button type="primary" @click="onEvent('conference')" v-if="m_bCallConnect">三方会议</el-button>
-		<el-button type="primary" @click="onEvent('evaluate')" v-if="m_bCallConnect">评价</el-button>
+		<el-button class="default-button" @click="onEvent('busy')" v-if="!m_bTelBusy && m_bLogin">示忙</el-button>
+		<el-button class="default-button" @click="onEvent('idle')" v-if="m_bTelBusy && m_bLogin">示闲</el-button>
+		<el-button class="default-button" @click="onEvent('hangup')" v-if="m_bCallConnect || m_isRing">挂机</el-button>
+		<el-button class="default-button" @click="onEvent('callOut')" v-if="m_bLogin && !m_isRing">呼</el-button>
+		<el-button class="default-button" @click="onEvent('hold')" v-if="!m_IsHold && m_bCallConnect">保持</el-button>
+		<el-button class="default-button" @click="onEvent('reHold')" v-if="m_IsHold && m_bCallConnect">恢复</el-button>
+		<el-button class="default-button" @click="onEvent('consult')" v-if="m_bCallConnect">咨询</el-button>
+		<el-button class="default-button" @click="onEvent('transferMz')" v-if="m_bCallConnect">盲转</el-button>
+		<el-button class="default-button" @click="onEvent('transfer')" v-if="m_bCallConnect">转接</el-button>
+		<el-button class="default-button" @click="onEvent('conference')" v-if="m_bCallConnect">三方会议</el-button>
+		<el-button class="default-button" @click="onEvent('evaluate')" v-if="m_bCallConnect">评价</el-button>
 	</div>
 
 	<!-- 签入弹窗 -->
 	<el-dialog v-model="state.dutyDialogVisible" draggable title="签入" width="500px" :show-close="false">
 		<el-form :model="state.dutyForm" label-width="80px" ref="dutyFormRef" @submit.native.prevent>
 			<el-form-item label="分机号" prop="telNo" :rules="[{ required: true, message: '请需要签入的分机号', trigger: 'change' }]">
-				<el-input v-model="state.dutyForm.telNo" placeholder="分机号"  @keyup.enter="clickOnDuty(dutyFormRef)"/>
+				<el-input v-model="state.dutyForm.telNo" placeholder="分机号" @keyup.enter="clickOnDuty(dutyFormRef)" />
 			</el-form-item>
 		</el-form>
 		<template #footer>
@@ -31,11 +67,11 @@
 		</template>
 	</el-dialog>
 
-	<!-- 呼弹窗 -->
-	<el-dialog v-model="state.outboundDialogVisible" draggable title="呼" width="450px">
+	<!-- 呼弹窗 -->
+	<el-dialog v-model="state.outboundDialogVisible" draggable title="呼" width="450px">
 		<el-form :model="state.outboundForm" label-width="80px" ref="outboundFormRef" @submit.native.prevent>
-			<el-form-item label="呼叫号码" prop="telNo" :rules="[{ required: true, message: '请填写呼叫号码', trigger: 'blur' }]">
-				<el-input v-model="state.outboundForm.telNo" placeholder="呼叫号码"  @keyup.enter="clickOnOutbound(outboundFormRef)"/>
+			<el-form-item label="呼出号码" prop="telNo" :rules="[{ required: true, message: '请填写呼出号码', trigger: 'blur' }]">
+				<el-input v-model="state.outboundForm.telNo" placeholder="呼出号码" @keyup.enter="clickOnOutbound(outboundFormRef)" />
 			</el-form-item>
 		</el-form>
 		<template #footer>
@@ -57,7 +93,7 @@
 				</el-radio-group>
 			</el-form-item>
 			<el-form-item label="咨询" prop="telNo" :rules="[{ required: true, message: '请填写咨询号码', trigger: 'blur' }]">
-				<el-input v-model="state.consultForm.telNo" placeholder="咨询号码"  @keyup.enter="clickOnConsult(consultFormRef)" />
+				<el-input v-model="state.consultForm.telNo" placeholder="咨询号码" @keyup.enter="clickOnConsult(consultFormRef)" />
 			</el-form-item>
 		</el-form>
 		<template #footer>
@@ -71,7 +107,7 @@
 	<el-dialog v-model="state.blindDialogVisible" draggable title="盲转" width="450px">
 		<el-form :model="state.blindForm" label-width="80px" ref="blindFormRef" @submit.native.prevent>
 			<el-form-item label="盲转" prop="telNo" :rules="[{ required: true, message: '请填写盲转号码', trigger: 'blur' }]">
-				<el-input v-model="state.blindForm.telNo" placeholder="盲转号码" @keyup.enter="clickOnBlind(blindFormRef)"/>
+				<el-input v-model="state.blindForm.telNo" placeholder="盲转号码" @keyup.enter="clickOnBlind(blindFormRef)" />
 			</el-form-item>
 		</el-form>
 		<template #footer>
@@ -86,19 +122,24 @@
 </template>
 
 <script setup lang="ts" name="zgTelControl">
-import { computed, onMounted, reactive, ref } from 'vue';
+import { onMounted, reactive, ref } from 'vue';
 import signalR from '@/utils/signalR';
-import mittBus from '@/utils/mitt';
 import { getNowDateTime } from '@/utils/constants';
 import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { useRouter } from 'vue-router';
 import { useWebSocket } from '@/hooks/useWebsocket';
+import { useIntervalFn } from '@vueuse/shared';
+import { formatDuration } from '@/utils/formatTime';
+import { Local } from '@/utils/storage';
+import { useAppConfig } from '@/stores/appConfig';
+import { storeToRefs } from 'pinia';
+import { useUserInfo } from '@/stores/userInfo';
 
 const state = reactive({
 	dutyDialogVisible: false,
 	loading: false,
 	dutyForm: {
-		telNo: '8001',
+		telNo: '',
 	},
 	outboundDialogVisible: false,
 	outboundForm: {
@@ -146,9 +187,9 @@ const e_TelSendMsg = (strObj: Object) => {
 		currentState.value = '签出';
 	}
 };
-const m_strUserNo = ref('8001'); // 分机号码
+const m_strUserNo = ref(''); // 分机号码
 const m_strJobNum = ref(''); // 坐席工号
-const m_strSkillId = ref('1'); // 技能组
+const m_strSkillId = ref(''); // 技能组
 const m_strLevel = ref('1'); // 优先级别
 const m_strGroup = ref('1'); // 分组ID
 const m_strCompanyId = ref(''); // 企业编码
@@ -173,6 +214,7 @@ const m_nStateMonitor = ref(0); // 坐席监控状态页面参数
 const m_IsMonListen = ref('0'); // 监控状态 0-未监听;1-监控成功;2-监控失败;
 const m_strTelState = ref('0'); // 当前状态
 const m_nCallTime = ref(0); // 实时通话时长
+const showPop = ref(false);
 // 开启链接
 /* JobNum:工号 Name:姓名 Extension:分机号 Extension:分机号 Extension:分机号 Role:角色,保留,不做设置 GroupName:技能组名称 GroupName:技能组名称 GroupName:技能组名称
   示例消息:
@@ -571,9 +613,89 @@ const ResAgentMonitor = (data) => {
 {“Action”:”ResAgentLogin”,”Param”:{“Result”:}}
 Result: 3:分机错误 7:已登录 0:登录成功
  */
+const signTime = ref(0); //签入时长
+// 使用 useInterval 创建定时器
+const signInTimer = useIntervalFn(
+	() => {
+		signTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 签入时长及时开始
+const startSignTime = (second?: number) => {
+	if (second) {
+		// 从后台获取签入时长
+		if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
+		signTime.value = second;
+		signInTimer.resume();
+	} else {
+		signInTimer.resume();
+	}
+};
+//  签入时长计时结束
+const stopSignTime = () => {
+	signTime.value = 0;
+	signInTimer.pause();
+};
+// 空闲时长
+const idleTime = ref(0);
+const idleTimer = useIntervalFn(
+	() => {
+		idleTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 空闲时长开始
+const startIdleTime = () => {
+	idleTimer.resume();
+	stopBusyTime();
+};
+//  空闲时长计时结束
+const stopIdleTime = () => {
+	idleTime.value = 0;
+	idleTimer.pause();
+};
+const appConfigStore = useAppConfig();
+const { AppConfigInfo } = storeToRefs(appConfigStore); // 系统配置信息
+const storesUserInfo = useUserInfo();
+const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
 const onSignIn = () => {
-	console.log(wsRef.value);
-	state.dutyDialogVisible = true;
+  console.log(userInfos.value)
+  if (!userInfos.value.defaultTelNo) {
+    ElMessage.error('请先配置用户默认分机');
+    return;
+  }
+  if (!userInfos.value.defaultTelGroup) {
+    ElMessage.error('请先配置用户技能组');
+    return;
+  }
+
+  state.loading = true;
+  m_strUserNo.value = userInfos.value.defaultTelNo; // 默认分机
+  m_strJobNum.value = <string>userInfos.value.staffNo; // 工号
+  m_strSkillId.value = userInfos.value.defaultTelGroup; // 技能组
+  wsRef.value.open();
+	/*if (AppConfigInfo.value.isNeedTelNo) {
+		// 需要填写分机号
+		state.dutyDialogVisible = true;
+	} else {
+		if (!userInfos.value.defaultTelNo) {
+			ElMessage.error('请先配置用户默认分机');
+			return;
+		}
+		if (!userInfos.value.defaultTelGroup) {
+			ElMessage.error('请先配置用户技能组');
+			return;
+		}
+
+		state.loading = true;
+		m_strUserNo.value = userInfos.value.defaultTelNo; // 默认分机
+		m_strJobNum.value = <string>userInfos.value.staffNo; // 工号
+		m_strSkillId.value = userInfos.value.defaultTelGroup; // 技能组
+		wsRef.value.open();
+	}*/
 };
 const dutyFormRef = ref();
 const clickOnDuty = (formEl: FormInstance | undefined) => {
@@ -582,7 +704,7 @@ const clickOnDuty = (formEl: FormInstance | undefined) => {
 		if (!valid) return;
 		state.loading = true;
 		m_strUserNo.value = state.dutyForm.telNo;
-    m_strJobNum.value = state.dutyForm.telNo;
+		m_strJobNum.value = state.dutyForm.telNo;
 		wsRef.value.open();
 		state.dutyDialogVisible = false;
 	});
@@ -603,6 +725,8 @@ const retSignIn = (data: any) => {
 		// 登录成功
 		currentState.value = '空闲';
 		ElMessage.success('签入成功');
+		startSignTime(); // 开启计时器
+		startIdleTime(); // 空闲计时器
 		if (m_strIsMonitor.value === '1') {
 			// 监控初始化状态
 			ReqAgentMonitor();
@@ -664,6 +788,7 @@ const onSignOut = () => {
 const retSignOut = () => {
 	currentState.value = '签出';
 	ElMessage.success('签出成功');
+
 	state.loading = false;
 	// 登出成功
 	m_strTelState.value = '0';
@@ -675,21 +800,57 @@ const retSignOut = () => {
 	m_bLogin.value = false;
 	// 关闭ws
 	wsRef.value.close();
+	stopSignTime(); // 停止签入计时器
+	stopBusyTime(); // 停止示忙计时器
+	stopIdleTime(); // 停止空闲计时器
+	stopTalkTimer(); // 停止通话计时器
 };
 /*
  * 示忙
  * ReqAgentBusy - 方法名
  * Extension:分机号
  */
+// 示忙时长
+const busyTime = ref(0);
+const busyTimer = useIntervalFn(
+	() => {
+		busyTime.value += 1;
+	},
+	1000,
+	{ immediate: false }
+);
+// 示忙时长开始
+const startBusyTime = () => {
+	busyTimer.resume();
+	stopIdleTime();
+};
+//  示忙时长计时结束
+const stopBusyTime = () => {
+	busyTime.value = 0;
+	busyTimer.pause();
+};
 const onBusy = () => {
-	const objMsg = {
-		Action: 'ReqAgentBusy',
-		Param: {
-			Extension: m_strUserNo.value,
-		},
-	};
-	// 发送请求
-	e_TelSendMsg(objMsg);
+	ElMessageBox.confirm(`确定要示忙,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			const objMsg = {
+				Action: 'ReqAgentBusy',
+				Param: {
+					Extension: m_strUserNo.value,
+				},
+			};
+			// 发送请求
+			e_TelSendMsg(objMsg);
+		})
+		.catch(() => {
+			state.loading = false;
+		});
 };
 
 /*
@@ -704,6 +865,7 @@ const retBusy = (data) => {
 		m_strTelState.value = '201';
 		e_TopStateChange(m_strTelState.value);
 		currentState.value = '示忙中';
+		startBusyTime();
 	} else {
 		ElMessage.error('示忙失败');
 	}
@@ -714,14 +876,27 @@ const retBusy = (data) => {
  * Extension:分机号
  */
 const onIdle = () => {
-	const objMsg = {
-		Action: 'ReqAgentIdle',
-		Param: {
-			Extension: m_strUserNo.value,
-		},
-	};
-	// 发送请求
-	e_TelSendMsg(objMsg);
+	ElMessageBox.confirm(`确定要示闲,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			const objMsg = {
+				Action: 'ReqAgentIdle',
+				Param: {
+					Extension: m_strUserNo.value,
+				},
+			};
+			// 发送请求
+			e_TelSendMsg(objMsg);
+		})
+		.catch(() => {
+			state.loading = false;
+		});
 };
 /*
 * 示闲返回值
@@ -735,7 +910,8 @@ const retIdle = (data) => {
 		m_strTelState.value = '200';
 		e_TopStateChange(m_strTelState.value);
 		// 示闲成功
-		currentState.value = '示闲中';
+		currentState.value = '空闲';
+		startIdleTime();
 	} else {
 		ElMessage.error('示闲失败');
 	}
@@ -879,7 +1055,6 @@ const clickOnConsult = (formEl: FormInstance | undefined) => {
 	});
 };
 const onConsult = (strCallNumber: string, strType: string) => {
-	console.log(strCallNumber, strType);
 	let strObj;
 	switch (strType) {
 		// 内线
@@ -1014,7 +1189,7 @@ const onTransferMz = (strCallNumber) => {
 	};
 	// 发送请求
 	e_TelSendMsg(objMsg);
-  state.loading = false;
+	state.loading = false;
 	// 结束挂机
 	retHangup();
 };
@@ -1023,14 +1198,28 @@ const onTransferMz = (strCallNumber) => {
  * 三方会议
  */
 const onConference = () => {
-	const objMsg = {
-		Action: 'ReqConference',
-		Param: {
-			Extension: m_strUserNo.value,
-		},
-	};
-	// 发送请求
-	e_TelSendMsg(objMsg);
+	ElMessageBox.confirm(`确定要发起三方会议,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			state.loading = true;
+			const objMsg = {
+				Action: 'ReqConference',
+				Param: {
+					Extension: m_strUserNo.value,
+				},
+			};
+			// 发送请求
+			e_TelSendMsg(objMsg);
+		})
+		.catch(() => {
+			state.loading = false;
+		});
 };
 
 /*
@@ -1038,6 +1227,7 @@ const onConference = () => {
  * { "Action": "ReqConference", "Param": {"Extension": "1002"} }
  */
 const retResConference = (data) => {
+	state.loading = false;
 	if (data.Param.Result == '0') {
 		// 咨询成功
 		m_IsConsult.value = true;
@@ -1097,6 +1287,7 @@ const retHangup = (data?: any) => {
 	callId.value = '';
 
 	currentState.value = '空闲';
+	stopTalkTimer();
 };
 /*
  * 评价
@@ -1110,7 +1301,7 @@ const i_evaluate = () => {
 	};
 	// 发送请求
 	e_TelSendMsg(objMsg);
-  state.loading = false;
+	state.loading = false;
 	// 结束挂机
 	retHangup();
 };
@@ -1274,6 +1465,29 @@ const evtCallAlerting = (data) => {
 * Callid:呼叫ID
 {“Event”:”EvtCallAnswer”,”Param”:{“Caller”:”123”,”Called”:”456”,”Callid”:”94593939”}}
 */
+const talkTime = ref(0); // 通话时长
+const talkTimer = useIntervalFn(
+	() => {
+		talkTime.value += 1;
+		Local.set('talkTime', String(talkTime.value));
+	},
+	1000,
+	{ immediate: false }
+);
+// 开始通话计时
+const startTalkTimer = () => {
+	let localTalkTime = Local.get('talkTime');
+	if (talkTime.value) {
+		talkTime.value = Number(localTalkTime);
+	}
+	talkTimer.resume();
+};
+// 结束通话计时
+const stopTalkTimer = () => {
+	talkTimer.pause();
+	talkTime.value = 0;
+	Local.remove('talkTime');
+};
 const evtEvtCallAnswer = (data) => {
 	let strCalledNum;
 	let strTelNumber;
@@ -1281,6 +1495,7 @@ const evtEvtCallAnswer = (data) => {
 	m_bCallConnect.value = true;
 	m_isRing.value = false;
 	currentState.value = '通话中';
+	startTalkTimer();
 	console.log(
 		`接通电话,是否呼出:${m_IsCallOut.value},是否咨询:${m_IsConsult.value},是否呼出弹屏:${m_CallOutOpen.value},是否弹屏:${
 			m_bIsOpen.value
@@ -1529,33 +1744,18 @@ const e_ActionUpdate = (strActionType: string) => {};
  */
 const e_TelSignOut = (strUserNum: string) => {};
 // 检查用户状态
-// 设置当前可用的按钮
-const activeBtns = computed(() => {
-	const switchCases: any = {
-		签出: ['signIn'],
-		空闲: ['signOut', 'rest', 'outbound'],
-		示忙: ['signOut', 'busy'],
-		通话中: ['hangup', 'hold', 'consult', 'transfer', 'transferMz', 'evaluate'],
-		振铃中: ['hangup'],
-		示忙中: ['signOut', 'idle'],
-		保持中: ['hangup', 'reHold'],
-		外呼中: ['hangup', 'hold', 'transfer', 'conference'],
-		三方会议中: ['hangup', 'hold', 'transfer', 'conference'],
-		话后整理中: ['hangup', 'rest', 'TalkingDeal'],
-	};
-	let arr = <EmptyArrayType>[];
-	if (currentState.value in switchCases) {
-		arr = switchCases[currentState.value];
-	}
-	return arr;
-});
 onMounted(async () => {
 	// 加入分组
 	await signalR.joinGroup('CallCenter');
-	mittBus.on('xtWsMessage', (data: any) => {
-		e_TelMsgReceive(data);
-	});
 	initWs();
+	// 是否在通话中
+	window.onbeforeunload = function (e) {
+		if (m_bCallConnect.value) {
+			const dialogText = '正在通话中,您确定要刷新吗?';
+			e.returnValue = dialogText;
+			return dialogText;
+		}
+	};
 });
 </script>
 
@@ -1567,9 +1767,36 @@ onMounted(async () => {
 	display: flex;
 	flex: 1;
 	background-color: var(--el-color-white) !important;
-	padding: 0 18px 0 40px;
+	padding: 0 10px;
 	color: var(--hotline-color-text-main);
 	height: 100%;
 	align-items: center;
+	.status-box {
+		width: 240px;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background: var(--el-color-info-light-7);
+		position: relative;
+		box-sizing: border-box;
+		cursor: pointer;
+		text-align: left;
+		font-size: 14px;
+		padding: 4px 26px 4px 12px;
+		gap: 6px;
+		min-height: 32px;
+		line-height: 24px;
+		border-radius: var(--el-border-radius-round);
+		background-color: var(--el-color-info-light-8);
+		transition: var(--el-transition-duration);
+		.arrow {
+			position: absolute;
+			right: 10px;
+			transition: transform var(--el-transition-duration);
+		}
+		.is-reverse {
+			transform: rotate(180deg);
+		}
+	}
 }
 </style>

+ 12 - 40
src/layout/navBars/tagsView/tagsView.vue

@@ -1,8 +1,5 @@
 <template>
-	<div
-		class="layout-navbars-tagsview"
-		:class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic', isShowControls: userInfosConfig.showTelControl }"
-	>
+	<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
 		<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
 			<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
 				<li
@@ -30,14 +27,14 @@
 							@click.stop="refreshCurrentTagsView($route.fullPath)"
 						/>
 						<SvgIcon
-							name="ele-CircleCloseFilled"
+							name="ele-Close"
 							class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
 							v-if="!v.meta.isAffix"
 							@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
 						/>
 					</template>
 					<SvgIcon
-						name="ele-CircleCloseFilled"
+						name="ele-Close"
 						class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
 						v-if="!v.meta.isAffix"
 						@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
@@ -615,7 +612,6 @@ watch(
 	// border-bottom: 1px solid var(--hotline-border-color-light);
 	position: relative;
 	z-index: 4;
-
 	:deep(.el-scrollbar__wrap) {
 		overflow-x: auto !important;
 	}
@@ -717,44 +713,37 @@ watch(
 	// 风格5
 	.tags-style-five {
 		align-items: flex-end;
-
 		.tags-style-five-svg {
-			// -webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
-			// 	12 27 15;
-			font-weight: normal;
+			-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
+				url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTg5Ljc2MjQgNy4zMzAxNCA1NS4xMjUyKSIgc3Ryb2tlPSJudWxsIiBpZD0ic3ZnXzEiIGZpbGw9IiNGOEVBRTciIGQ9Im02Mi41NzQ0OSwxMTcuNTIwODZjLTU1LjIyOCwwIC0xMDAsLTQ0Ljc3MiAtMTAwLC0xMDBsMCwxMDBsMTAwLDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGQ9Im0tMC42Mzc2Niw3LjMxMjI4YzAuMTE5MTksMCAwLjIxNzM3LDAuMDU3OTYgMC40NzY3NiwwLjExOTE5YzAuMjMyLDAuMDU0NzcgMC4yNzMyOSwwLjAzNDkxIDAuMzU3NTcsMC4xMTkxOWMwLjA4NDI4LDAuMDg0MjggMC4zNTc1NywwIDAuNDc2NzYsMGwwLjExOTE5LDBsMC4yMzgzOCwwIiBpZD0ic3ZnXzIiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Im0yOC45MjEzNCw2OS4wNTI0NGMwLDAuMTE5MTkgMCwwLjIzODM4IDAsMC4zNTc1N2wwLDAuMTE5MTlsMCwwLjExOTE5IiBpZD0ic3ZnXzMiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNCIgaGVpZ2h0PSIwIiB3aWR0aD0iMS4zMTEwOCIgeT0iNi44MzU1MiIgeD0iLTAuMDQxNzEiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNSIgaGVpZ2h0PSIxLjc4Nzg0IiB3aWR0aD0iMC4xMTkxOSIgeT0iNjguNDU2NSIgeD0iMjguOTIxMzQiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNiIgaGVpZ2h0PSI0Ljg4Njc3IiB3aWR0aD0iMTkuMDcwMzIiIHk9IjUxLjI5MzIxIiB4PSIzNi42Njg2NiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiA8L2c+Cjwvc3ZnPg=='),
+				url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='8' width='100%' height='100%' fill='%23F8EAE7'/></svg>");
+			-webkit-mask-size: 18px 30px, 20px 30px, calc(100% - 30px) calc(100% + 17px);
+			-webkit-mask-position: right bottom, left bottom, center top;
+			-webkit-mask-repeat: no-repeat;
 		}
-
 		.layout-navbars-tagsview-ul-li {
 			padding: 0 5px;
-			border-width: 15px 20px 15px 15px;
+			border-width: 15px 27px 15px;
 			border-style: solid;
 			border-color: transparent;
-			border-radius: 0 16px 0 0;
-
+			margin: 0 -15px;
 			.layout-icon-active,
 			.layout-navbars-tagsview-ul-li-iconfont,
 			.layout-navbars-tagsview-ul-li-refresh {
 				display: none;
 			}
-
 			.layout-icon-three {
 				display: block;
 			}
-
 			&:hover {
 				@extend .tags-style-five-svg;
 				background: var(--el-color-primary-light-9);
 				color: unset;
 			}
 		}
-
-		.layout-navbars-tagsview-ul-li-icon {
-			color: var(--hotline-tagsview-icon-color);
-		}
-
 		.is-active {
 			@extend .tags-style-five-svg;
-			background: var(--hotline-bg-main-color) !important;
+			background: var(--el-color-primary-light-9) !important;
 			color: var(--el-color-primary) !important;
 			z-index: 1;
 		}
@@ -762,23 +751,6 @@ watch(
 }
 
 .isShowControls {
-	background: none;
-	margin-top: 15px;
-
-	.tags-style-five {
-		.layout-navbars-tagsview-ul-li {
-			&:hover {
-				@extend .tags-style-five-svg;
-				// background: var(--el-color-primary-light-8);
-				color: unset;
-			}
-		}
-
-		.is-active {
-			@extend .tags-style-five-svg;
-			background-color: var(--el-color-white) !important;
-		}
-	}
 }
 
 .layout-navbars-tagsview-shadow {

+ 16 - 0
src/router/route.ts

@@ -404,6 +404,22 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
 			title: '部门满意度列表明细',
 			isKeepAlive: true,
 		},
+	},{
+		path: '/statistics/department/detailHandleList',
+		name: 'statisticsDepartmentDetailHandleList',
+		component: () => import('@/views/statistics/department/detailHandleList.vue'),
+		meta: {
+			title: '部门办件列表明细',
+			isKeepAlive: true,
+		},
+	},{
+		path: '/statistics/department/detailOverdueList',
+		name: 'statisticsDepartmentDetailOverdueList',
+		component: () => import('@/views/statistics/department/detailOverdueList.vue'),
+		meta: {
+			title: '部门超期列表明细',
+			isKeepAlive: true,
+		},
 	}
 ];
 

+ 3 - 1
src/stores/userInfo.ts

@@ -20,6 +20,7 @@ export const useUserInfo = defineStore('userInfo', {
 			photo: '', //头像
 			authBtnList: [], // 授权按钮数组
 			defaultTelNo: '', // 默认分机号
+			defaultTelGroup: '', // 默认分机组
 			token: '',
 			showTelControl: false, // 是否展示坐席操作电话面板
 			orgName: '', // 组织名称
@@ -46,7 +47,8 @@ export const useUserInfo = defineStore('userInfo', {
 				this.userInfos.account = userInfo.result?.user.account ?? '';
 				this.userInfos.phoneNo = userInfo.result?.user.phoneNo ?? '';
 				this.userInfos.staffNo = userInfo.result?.user.staffNo ?? '';
-				this.userInfos.defaultTelNo = userInfo.result.defaultTelNo ?? '';
+				this.userInfos.defaultTelNo = userInfo.result.user.defaultTelNo ?? '';
+				this.userInfos.defaultTelGroup = userInfo.result.user.defaultTelGroup ?? '';
 				this.userInfos.id = userInfo.result?.user.id ?? '';
 				this.userInfos.roles = userInfo.result?.user.roles ?? [];
 				this.userInfos.token = Cookie.get('token') ?? '';

+ 2 - 2
src/theme/media/scrollbar.scss

@@ -1,10 +1,10 @@
 @import './index.scss';
 
 .el-scrollbar__bar.is-vertical {
-	width: 8px !important;
+	width: 6px !important;
 }
 .el-scrollbar__bar.is-horizontal {
-	height: 8px !important;
+	height: 6px !important;
 }
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 0
src/types/pinia.d.ts

@@ -11,6 +11,7 @@ declare interface UserInfosState {
 	phoneNo: string;
 	staffNo?: string;
 	defaultTelNo?: string;
+	defaultTelGroup:string;
 	deletionTime?: string;
 	isDeleted?: boolean;
 	showTelControl: boolean;

+ 0 - 408
src/utils/ola_api.ts

@@ -1,408 +0,0 @@
-import { dutyOff } from '@/api/public/wex';
-/**
- * @description 呼叫中心对接接口
- */
-import { ElMessage } from 'element-plus';
-import { useWebSocket } from '@/hooks/useWebsocket';
-
-export const ola: any = {
-	version: '2.0.1',
-	ws: undefined,
-	uuid: '73836387-0000-0000-0000-0000-0000000000',
-	connected: false,
-	socket_connected: false,
-	onConnect: undefined,
-	onMessage: undefined,
-	onClose: undefined,
-	_url: undefined,
-	_connectSuccess: undefined,
-	_connectError: undefined,
-	_username: undefined,
-	_password: undefined,
-	_extn: undefined,
-	heartbeatIntervalTimer: null, // 心跳定时器
-	heartbeatInterval: 5 * 1000, // 心跳间隔
-	reconnectInterval: 4 * 1000, // 重连间隔
-	maxReconnectAttempts: 99, // 最大重连次数
-	reconnectAttempts: 0, // 重连次数
-	isReconnect: true, // 是否需要重连
-
-	connect: function (url: string | URL, username: any, password: any, success?: any, error?: any) {
-		if (!url) url = 'ws://' + document.location.hostname + ':' + document.location.port + '/ola_socket';
-		if ('WebSocket' in window) {
-			// browser supports websockets
-			this.ws = new WebSocket(url);
-			if (this.ws) {
-				this.ws.onclose = ola._onClose;
-				this.ws.onopen = ola._onOpen;
-				this.ws.onmessage = ola._onMessage;
-				this._url = url;
-				this._connectSuccess = success;
-				this._connectError = error;
-				this._username = username;
-				this._password = password;
-			}
-		} else {
-			// browser does not support websockets
-			ElMessage.error('您的浏览器不支持websocket!');
-			if ('console' in window) {
-				console.log("you don't have websocket");
-			}
-			return false;
-		}
-		return this;
-	},
-
-	close: function () {
-		// 手动关闭
-		ola.isReconnect = false;
-		ola.ws?.close();
-		ola.ws = null;
-		ola.stopHeartbeat();
-	},
-
-	_onOpen: function () {
-		ola.auth(ola._username, ola._password);
-		ola.startSubscribe();
-		ola.startHeartbeat();
-	},
-
-	_onMessage: function (evt: any) {
-		//获取消息
-		const msg = JSON.parse(evt.data);
-		if (msg.event_type == 'command/reply' && msg.code !== 200) {
-			//授权失败 调用签出接口
-			ElMessage.error(msg.text);
-			dutyOff();
-		}
-		if (msg.event_name == 'command/reply' && msg.code == 200) {
-			ola.ws.onmessage = this.onMessage;
-		}
-		ola.ws.onmessage = ola.onMessage;
-		ola.ws.onclose = ola.onClose;
-		ola.onConnect();
-
-	},
-	_onClose: function () {
-		ola.stopHeartbeat();
-		// @ts-ignore
-		if (ola.isReconnect) {
-			ola.reConnect();
-		}
-	},
-	reConnect: function () {
-		// @ts-ignore
-		if (ola.reconnectAttempts < ola.maxReconnectAttempts) {
-			setTimeout(() => {
-				ola.reconnectAttempts++;
-				ola.connect(this._url, this._username, this._password, this._connectSuccess, this._connectError);
-			}, ola.reconnectInterval);
-		} else {
-			console.error('已到达重连次数最高,请手动刷新重连');
-		}
-	},
-	startHeartbeat: function () {
-		if (!ola.heartbeatInterval) return;
-		ola.heartbeatIntervalTimer = window.setInterval(() => {
-			if (ola.ws?.readyState === WebSocket.OPEN) {
-				ola.ping();
-			}
-		}, ola.heartbeatInterval);
-	},
-	stopHeartbeat: function () {
-		if (ola.heartbeatIntervalTimer) {
-			clearInterval(ola.heartbeatIntervalTimer);
-			ola.heartbeatIntervalTimer = null;
-		}
-	},
-	startSubscribe: function () {
-		ola.subscribe('ola.agent.' + ola._extn);
-		ola.subscribe('ola.caller.' + ola._extn);
-		ola.get_agent_state(ola._extn);
-	},
-	auth: function (username: any, password: any) {
-		return ola.send({ cmd: 'auth', args: { username, password, accept: 'application/json' } });
-	},
-
-	get_agent_state: function (extn: any) {
-		return this.send({ action: 'api', cmd: 'get_agent_state', args: { extn } });
-	},
-
-	get_trunk_state: function () {
-		return this.send({ action: 'api', cmd: 'get_trunk_state' });
-	},
-
-	login: function (queue: any, extn: any, params: any) {
-		const args = { queue, extn };
-		this.merge(args, params);
-		this._extn = extn;
-		return ola.send({ action: 'api', cmd: 'login', args: args });
-	},
-
-	logout: function (extn?: string) {
-		const extns = extn || this._extn;
-		return this.send({ action: 'api', cmd: 'logout', args: { extn: extns } });
-	},
-
-	collect_dtmf: function (extn: any, soundfile: any) {
-		return this.send({ action: 'api', cmd: 'collect_dtmf', args: { extn, soundfile } });
-	},
-	collect_judge: function (extn: any) {
-		return this.send({ action: 'api', cmd: 'collect_judge', args: { extn: extn } });
-	},
-
-	ping: function () {
-		return ola.send({ cmd: 'ping' });
-	},
-
-	subscribe: function (k: any) {
-		return ola.send({ cmd: 'subscribe', args: { key: k } });
-	},
-
-	unsubscribe: function (k: any) {
-		return ola.send({ cmd: 'unsubscribe', args: { key: k } });
-	},
-
-	go_ready: function () {
-		return this.send({ action: 'api', cmd: 'go_ready', args: { extn: this._extn } });
-	},
-
-	go_ready2: function (extn: any) {
-		return this.send({ action: 'api', cmd: 'go_ready', args: { extn: extn } });
-	},
-
-	go_break: function (reason: any) {
-		return this.send({ action: 'api', cmd: 'go_break', args: { extn: this._extn, reason: reason } });
-	},
-
-	go_break2: function (extn: any) {
-		return this.send({ action: 'api', cmd: 'go_break', args: { extn: extn, reason: '' } });
-	},
-
-	toggle_ready: function () {
-		return this.send({ action: 'api', cmd: 'toggle_ready', args: { extn: this._extn } });
-	},
-
-	answer: function () {
-		return this.send({ action: 'api', cmd: 'answer', args: { extn: this._extn } });
-	},
-
-	hangup: function () {
-		return this.send({ action: 'api', cmd: 'hangup_other', args: { extn: this._extn } });
-	},
-
-	dial: function (dst: any, otherStr?: any, gateway?: any) {
-		return this.send({ action: 'api', cmd: 'dial', args: { extn: this._extn, dest: dst, gateway: gateway, otherStr: otherStr } });
-	},
-
-	transfer: function (dst: any, src?: any) {
-		let extn = src;
-
-		if (extn == null || typeof extn == 'undefined') {
-			extn = this._extn;
-		}
-
-		return this.send({ action: 'api', cmd: 'transfer', args: { extn: extn, dest: dst } });
-	},
-
-	transfer_uuid: function (uuid: any, dst: any) {
-		return this.send({ action: 'api', cmd: 'transfer', args: { channel_uuid: uuid, dest: dst } });
-	},
-
-	monitor: function (dst: any, src: any) {
-		let extn = src;
-		if (extn == null || typeof extn == 'undefined') {
-			extn = this._extn;
-		}
-
-		return this.send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst } });
-	},
-
-	monitor_uuid: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'monitor', args: { extn: this._extn, channel_uuid: uuid } });
-	},
-
-	intercept: function (dst: any, src: any, gateway: any) {
-		let extn = src;
-
-		if (extn == null || typeof extn == 'undefined') {
-			extn = this._extn;
-		}
-
-		return this.send({ action: 'api', cmd: 'intercept', args: { extn: extn, dest: dst } });
-	},
-
-	intercept_uuid: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'intercept', args: { extn: this._extn, channel_uuid: uuid } });
-	},
-
-	three_way: function (dst: any, src: any, gateway?: any) {
-		let extn = src;
-		if (extn == null || typeof extn == 'undefined') {
-			extn = this._extn;
-		}
-		return this.send({ action: 'api', cmd: 'monitor', args: { extn: extn, dest: dst, three_way: 'true', goip_gateway: gateway } });
-	},
-	/* hangup the thrid party */
-	exit_three_way: function (ext: any) {
-		return this.send({ action: 'api', cmd: 'unmonitor', args: { extn: ext, three_way: 'true' } });
-	},
-	three_way_uuid: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'monitor', args: { extn: this._extn, channel_uuid: uuid, three_way: 'true' } });
-	},
-
-	unmonitor: function (ext: any) {
-		return this.send({ action: 'api', cmd: 'unmonitor', args: { extn: ext } });
-	},
-
-	whisper: function (w: any) {
-		// who = "agent" / "caller" / "both" / "none"
-		return this.send({ action: 'api', cmd: 'whisper', args: { extn: this._extn, who: w } });
-	},
-
-	consult: function (dst: any) {
-		return this.send({ action: 'api', cmd: 'consult', args: { extn: this._extn, dest: dst } });
-	},
-	unconsult: function (dst: any) {
-		return this.send({ action: 'api', cmd: 'unconsult', args: { extn: this._extn, dest: dst } });
-	},
-	consult_to_three_way: function (dst: any) {
-		return this.send({ action: 'api', cmd: 'consult_to_three_way', args: { extn: this._extn, dest: dst } });
-	},
-
-	hold: function () {
-		return this.send({ action: 'api', cmd: 'hold', args: { extn: this._extn } });
-	},
-
-	unhold: function () {
-		return this.send({ action: 'api', cmd: 'unhold', args: { extn: this._extn } });
-	},
-
-	toggle_hold: function () {
-		return this.send({ action: 'api', cmd: 'toggle_hold', args: { extn: this._extn } });
-	},
-
-	take_call: function (channel_uuid: any) {
-		return this.send({ action: 'api', cmd: 'take_call', args: { extn: this._extn, channel_uuid: channel_uuid } });
-	},
-	next_queue: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'next_queue', args: { extn: this._extn, channel_uuid: uuid } });
-	},
-
-	conference: function (dst: any) {
-		return this.send({ action: 'api', cmd: 'conference', args: { extn: this._extn, dest: dst } });
-	},
-
-	conference_uuid: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'conference', args: { extn: this._extn, channel_uuid: uuid } });
-	},
-
-	broadcast: function (numbers: any, mute: any) {
-		return this.send({ action: 'api', cmd: 'broadcast', args: { extn: this._extn, numbers, mute } });
-	},
-
-	/* send chat to a queue or an agent
-      to = queue             send to queue
-      to = queue.agent       send to agent
-    */
-	chat: function (to: any, message: any, content_type: any) {
-		return this.send({ action: 'api', cmd: 'chat', args: { to: to, message: message, content_type: content_type } });
-	},
-
-	message: function (from: any, to: any, message: any, content_type: any) {
-		return this.send({ action: 'api', cmd: 'message', args: { from: from, to: to, message: message, content_type: content_type } });
-	},
-
-	alarm: function (queue: any, state: any) {
-		return this.send({ action: 'api', cmd: 'alarm', args: { queue, state } });
-	},
-
-	/* dispatching apis */
-
-	dlogin: function (ext: any) {
-		return this.send({ action: 'api', cmd: 'dlogin', args: { extn: ext } });
-	},
-
-	dlogout: function (ext: any) {
-		return this.send({ action: 'api', cmd: 'dlogout', args: { extn: ext } });
-	},
-
-	inject: function (ext: any, uuid: any) {
-		return this.send({ action: 'api', cmd: 'inject', args: { extn: ext, channel_uuid: uuid } });
-	},
-
-	kill: function (uuid: any, extn: any) {
-		return this.send({ action: 'api', cmd: 'kill', args: { channel_uuid: uuid, extn: extn } });
-	},
-
-	eavesdrop: function (uuid: any) {
-		return this.send({ action: 'api', cmd: 'eavesdrop', args: { channel_uuid: uuid } });
-	},
-
-	conf: function (name: any, action: any, member: any) {
-		return this.send({ action: 'api', cmd: 'conf', args: { name: name, action: action, member: member } });
-	},
-
-	answer_all: function (ext: any, queue: any) {
-		return this.send({ action: 'api', cmd: 'answer_all', args: { extn: ext, queue: queue } });
-	},
-
-	group_call: function (ext: any, queue: any, numbers: any, batch_accept: any) {
-		return this.send({ action: 'api', cmd: 'group_call', args: { extn: ext, queue: queue, numbers: numbers, batch_accept: batch_accept } });
-	},
-
-	sip_gateway: function (profile: any, gateway: any, op: any) {
-		return this.send({ action: 'api', cmd: 'sip_gateway', args: { profile: profile, gateway: gateway, op: op } });
-	},
-
-	playback: function (filename: any) {
-		return this.send({ action: 'api', cmd: 'playback', args: { extn: this._extn, soundfile: filename } });
-	},
-
-	stop_playback: function () {
-		return this.send({ action: 'api', cmd: 'stop_playback', args: { extn: this._extn } });
-	},
-
-	merge_call: function (ext1: any, ext2: any) {
-		return this.send({ action: 'api', cmd: 'merge_call', args: { extn1: ext1, extn2: ext2 } });
-	},
-
-	/* common apis*/
-
-	/*phone control api, only yealink support for now*/
-	api_handfree: function (ext: any) {
-		return this.send({ action: 'api', cmd: 'api_handfree', args: { extn: ext } });
-	},
-	status: function () {
-		return ola.ws.readyState;
-	},
-
-	send: function (msg: any) {
-		//发送消息
-		if (!ola.ws) {
-			ElMessage.warning('请先签入');
-			return;
-		}
-		msg.uuid = ola.next_uuid();
-		ola.ws.send(JSON.stringify(msg));
-		return msg.uuid;
-	},
-
-	merge: function (target: { [x: string]: any }, additional: { [x: string]: any; hasOwnProperty: (arg0: string) => any }) {
-		for (const i in additional) {
-			if (additional.hasOwnProperty(i)) {
-				target[i] = additional[i];
-			}
-		}
-	},
-
-	next_uuid: function () {
-		let u = (parseFloat(ola.uuid.substring(29)) + 1).toString();
-		u = u == '2147483647' ? '0' : u;
-		while (u.length < 12) {
-			u = '0' + u;
-		}
-		ola.uuid = '73836387-0000-0000-0000-0000-' + u;
-		return ola.uuid;
-	},
-};

+ 13 - 22
src/views/business/return/audit.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="business-special-audit-container layout-pd">
+	<div class="business-return-audit-container layout-pd">
 		<!-- 搜索  -->
 		<el-card shadow="never">
 			<el-tabs v-model="state.queryParams.AuditState" @tab-change="handleQuery">
@@ -85,6 +85,7 @@ import { FormInstance } from 'element-plus';
 import { useRouter } from 'vue-router';
 import { returnAuditList } from '@/api/business/return';
 import { defaultTimeStartEnd, shortcuts } from "@/utils/constants";
+import Other from "@/utils/other";
 // 引入组件
 const ReturnAudit = defineAsyncComponent(() => import('@/views/business/return/components/Return-audit.vue')); // 审批
 const ReturnAuditMultiple = defineAsyncComponent(() => import('@/views/business/return/components/Return-audit-multiple.vue'));
@@ -111,7 +112,7 @@ const columns = ref<any[]>([
 	{ prop: 'content', label: '申请理由', width: 200 },
 	{ prop: 'operation', label: '操作', fixed: 'right', width: 170, align: 'center' },
 ]);
-const state = reactive({
+const state = reactive<any>({
 	queryParams: {
 		// 查询条件
 		PageIndex: 1,
@@ -119,6 +120,9 @@ const state = reactive({
 		Keyword: null, // 关键字
 		State: null, // 审批状态
 		AuditState: '1', // 是否已处理 1 待审批 2已审批
+    crTime:[],
+    CreationTimeStart:null,
+    CreationTimeEnd:null
 	},
 	tableData: [], //表单
 	loading: false, // 加载
@@ -130,24 +134,15 @@ const handleQuery = () => {
 	queryList();
 };
 /** 获取列表 */
+const requestParams =  ref({})
 const queryList = () => {
 	state.loading = true;
-	let CreationTimeStart = null;
-	let CreationTimeEnd = null;
-	if (state.queryParams?.crTime) {
-		CreationTimeStart = state.queryParams?.crTime[0];
-		CreationTimeEnd = state.queryParams?.crTime[1];
-	}
-	const request = {
-		CreationTimeStart,
-		CreationTimeEnd,
-		State: state.queryParams.AuditState === '2' ? state.queryParams.State : null,
-		PageIndex: state.queryParams.PageIndex,
-		PageSize: state.queryParams.PageSize,
-		Keyword: state.queryParams.Keyword,
-		AuditState: state.queryParams.AuditState,
-	};
-	returnAuditList(request)
+  requestParams.value = Other.deepClone(state.queryParams);
+  requestParams.value.CreationTimeStart = state.queryParams.crTime === null ? null : state.queryParams.crTime[0]; // 生成时间
+  requestParams.value.CreationTimeEnd = state.queryParams.crTime === null ? null : state.queryParams.crTime[1];
+  Reflect.deleteProperty( requestParams.value, 'crTime'); // 删除无用的参数
+  requestParams.value.AuditState === '2' ? state.queryParams.State : null
+	returnAuditList(requestParams.value)
 		.then((res) => {
 			state.tableData = res.result?.items ?? [];
 			state.total = res.result?.total ?? 0;
@@ -165,10 +160,6 @@ const resetQuery = (formEl: FormInstance | undefined) => {
 	formEl.resetFields();
 	queryList();
 };
-// 导出
-const onExport = () => {
-	console.log('导出');
-};
 // 审批
 const returnAuditRef = ref<RefType>();
 const onAudit = (row: any) => {

+ 18 - 18
src/views/business/return/components/Return-audit-detail.vue

@@ -1,11 +1,8 @@
 <template>
-	<el-dialog v-model="state.dialogVisible" draggable title="退回审批详情" width="50%" append-to-body destroy-on-close>
+	<el-dialog v-model="state.dialogVisible" draggable title="退回详情" width="50%" append-to-body destroy-on-close>
 		<div class="collapse-container">
 			<el-form label-width="110px" :model="state.ruleForm" class="show-info-form">
 				<el-row :gutter="35">
-					<el-divider content-position="left">
-						<el-text tag="b" size="large"> 退回详情 </el-text>
-					</el-divider>
 					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
 						<el-form-item label="工单编码"> {{ state.detail.order?.no }} </el-form-item>
 					</el-col>
@@ -24,9 +21,10 @@
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 						<el-form-item label="申请理由"> {{ state.detail.content }} </el-form-item>
 					</el-col>
-					<el-divider content-position="left">
-						<el-text tag="b" size="large"> 审批详情 </el-text>
-					</el-divider>
+				<template v-if="state.detail?.auditTime">
+          <el-divider content-position="left">
+            <el-text tag="b" size="large"> 审批详情 </el-text>
+          </el-divider>
           <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
             <el-form-item label="审批人">
               {{ state.detail?.auditUser }}
@@ -37,16 +35,17 @@
               {{ formatDate(state.detail?.auditTime, 'YYYY-mm-dd HH:MM:SS') }}
             </el-form-item>
           </el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-						<el-form-item label="审批结果">
-							{{ state.detail.state === 1 ? '审批通过' : '审批拒绝' }}
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-						<el-form-item label="审批意见" prop="auditContent">
-							{{ state.detail.auditContent }}
-						</el-form-item>
-					</el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="审批结果">
+              {{ state.detail.state === 1 ? '审批通过' : '审批拒绝' }}
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="审批意见" prop="auditContent">
+              {{ state.detail.auditContent }}
+            </el-form-item>
+          </el-col>
+        </template>
 				</el-row>
 			</el-form>
 		</div>
@@ -71,7 +70,8 @@ const openDialog = async (val: any) => {
 	state.dialogVisible = true;
 	try {
 		const { result } = await returnAuditDetail(val.id);
-		state.detail = result;
+    if(result) state.detail = result;
+		else state.detail = val;
 	} catch (e) {
 		console.log(e);
 	} finally {

+ 194 - 0
src/views/business/return/index.vue

@@ -0,0 +1,194 @@
+<template>
+	<div class="business-return-container layout-pd">
+		<el-card shadow="never">
+      <div class="flex-center-align mb20" v-auth="'business:return:querySelf'">
+        <span class="fast-search-label">数据范围</span>
+        <el-radio-group v-model="state.queryParams.QuerySelf" @change="handleQuery">
+          <el-radio-button label="true">我的</el-radio-button>
+          <el-radio-button label="false">全部</el-radio-button>
+        </el-radio-group>
+      </div>
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent label-width="100px">
+				<el-row :gutter="0">
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label="工单标题" prop="Keyword">
+							<el-input v-model="state.queryParams.Keyword" placeholder="工单标题" clearable @keyup.enter="handleQuery" />
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label="工单编码" prop="Keyword">
+							<el-input v-model="state.queryParams.Keyword" placeholder="工单编码" clearable @keyup.enter="handleQuery"/>
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label="审批状态" prop="State">
+							<el-select v-model="state.queryParams.State" placeholder="请选择审批状态" @change="handleQuery" class="w100">
+								<el-option value="0" label="待审核" />
+								<el-option value="1" label="审批通过" />
+								<el-option value="2" label="审批拒绝" />
+							</el-select>
+						</el-form-item>
+					</el-col>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-show="!searchCol">
+							<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="handleQuery"
+									value-format="YYYY-MM-DD[T]HH:mm:ss"
+									:default-time="defaultTimeStartEnd"
+								/>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label=" ">
+							<div class="flex-end w100">
+								<el-button type="primary" @click="handleQuery" :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-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<ProTable
+				ref="proTableRef"
+				:columns="columns"
+				:data="state.tableData"
+				@updateTable="queryList"
+				:loading="state.loading"
+				:total="state.total"
+				v-model:page-index="state.queryParams.PageIndex"
+				v-model:page-size="state.queryParams.PageSize"
+			>
+				<template #title="{ row }">
+					<order-detail :order="row.order" @updateList="queryList">{{ row.order?.title }}</order-detail>
+				</template>
+				<!-- 表格操作 -->
+				<template #operation="{ row }">
+					<el-button link type="primary" @click="onAuditDetail(row)" title="查看退回详情"> 退回详情 </el-button>
+					<order-detail :order="row.order" @updateList="queryList" />
+				</template>
+			</ProTable>
+		</el-card>
+		<!-- 审批详情 -->
+		<return-audit-detail ref="returnAuditDetailRef" />
+	</div>
+</template>
+<script setup lang="tsx" name="businessReturn">
+import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
+import { FormInstance } from 'element-plus';
+import { useRouter } from 'vue-router';
+import { returnList } from '@/api/business/return';
+import { defaultTimeStartEnd, shortcuts } from '@/utils/constants';
+import Other from '@/utils/other';
+// 引入组件
+const ReturnAuditDetail = defineAsyncComponent(() => import('@/views/business/return/components/Return-audit-detail.vue')); // 审批详情
+const OrderDetail = defineAsyncComponent(() => import('@/components/OrderDetail/index.vue')); // 工单详情
+// 定义变量内容
+const ruleFormRef = ref<RefType>(); // 表单ref
+const router = useRouter(); // 路由
+const proTableRef = ref<RefType>(); // 表格ref
+// 表格配置项
+const columns = ref<any[]>([
+	{ type: 'selection', fixed: 'left', width: 55, align: 'center' },
+	{ prop: 'order.no', label: '工单编码', width: 150 },
+	{ prop: 'order.isProvinceText', label: '省/市工单', width: 100 },
+	{ prop: 'order.title', label: '工单标题', width: 300 },
+	{ prop: 'order.sourceChannel', label: '来源方式' },
+	{ prop: 'order.acceptType', label: '受理类型', width: 150 },
+	{ prop: 'order.hotspotName', label: '热点分类', width: 200 },
+	{ prop: 'order.acceptorName', label: '受理人', width: 170 },
+	{ prop: 'order.orgLevelOneName', label: '一级部门', width: 170 },
+	{ prop: 'stateText', label: '退回审批状态', width: 120 },
+	{ prop: 'creatorName', label: '申请人', width: 120 },
+	{ prop: 'creatorOrgName', label: '申请部门', width: 150 },
+	{ prop: 'content', label: '申请理由', width: 200 },
+	{ prop: 'operation', label: '操作', fixed: 'right', width: 170, align: 'center' },
+]);
+const state = reactive<any>({
+	queryParams: {
+		// 查询条件
+		PageIndex: 1,
+		PageSize: 10,
+		Keyword: null, // 关键字
+		State: null, // 审批状态
+		crTime: [],
+		CreationTimeStart: null,
+		CreationTimeEnd: null,
+    QuerySelf:'true', // 数据权限
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
+});
+const searchCol = ref(true); // 展开/收起
+// 展开/收起
+const closeSearch = () => {
+	searchCol.value = !searchCol.value;
+};
+// 手动查询,将页码设置为1
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+/** 获取列表 */
+const requestParams = ref({});
+const queryList = () => {
+	state.loading = true;
+	requestParams.value = Other.deepClone(state.queryParams);
+	requestParams.value.CreationTimeStart = state.queryParams.crTime === null ? null : state.queryParams.crTime[0]; // 生成时间
+	requestParams.value.CreationTimeEnd = state.queryParams.crTime === null ? null : state.queryParams.crTime[1];
+	Reflect.deleteProperty(requestParams.value, 'crTime'); // 删除无用的参数
+	returnList(requestParams.value)
+		.then((res) => {
+			state.tableData = res.result?.items ?? [];
+			state.total = res.result?.total ?? 0;
+			state.loading = false;
+		})
+		.catch((err) => {
+			console.log(err);
+			state.loading = false;
+		});
+};
+
+/** 重置按钮操作 */
+const resetQuery = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	queryList();
+};
+// 审批详情
+const returnAuditDetailRef = ref<RefType>();
+const onAuditDetail = (row: any) => {
+	returnAuditDetailRef.value.openDialog(row);
+};
+onMounted(() => {
+	queryList();
+});
+</script>
+<style scoped lang="scss">
+.business-return-container {
+	.arrow {
+		transition: transform var(--el-transition-duration);
+		cursor: pointer;
+	}
+	.arrow.is-reverse {
+		transform: rotateZ(-180deg);
+	}
+}
+</style>

+ 2 - 2
src/views/business/return/province.vue

@@ -73,7 +73,7 @@ import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
 import { ElMessage, FormInstance } from 'element-plus';
 import { formatDate } from '@/utils/formatTime';
 import { useRouter } from 'vue-router';
-import { returnList } from '@/api/business/return';
+import { provinceReturnList } from '@/api/business/return';
 
 // 引入组件
 const OrderDetail = defineAsyncComponent(() => import('@/components/OrderDetail/index.vue')); // 工单详情
@@ -152,7 +152,7 @@ const handleQuery = () => {
 /** 获取列表 */
 const queryList = () => {
 	state.loading = true;
-	returnList(state.queryParams)
+	provinceReturnList(state.queryParams)
 		.then((res) => {
 			state.tableData = res.result?.items ?? [];
 			state.total = res.result?.total ?? 0;

+ 243 - 0
src/views/statistics/department/detailHandleList.vue

@@ -0,0 +1,243 @@
+<template>
+	<div class="statistics-department-satisfaction-detail-list-container layout-pd">
+		<el-card shadow="never">
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent label-width="100px" inline>
+				<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="handleQuery"
+						value-format="YYYY-MM-DD[T]HH:mm:ss"
+						:default-time="defaultTimeStartEnd"
+						:clearable="false"
+					/>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="handleQuery" :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-form-item>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<ProTable
+				ref="proTableRef"
+				:columns="columns"
+				:data="state.tableData"
+				@updateTable="queryList"
+				:loading="state.loading"
+				:total="state.total"
+				v-model:page-index="state.queryParams.PageIndex"
+				v-model:page-size="state.queryParams.PageSize"
+				:toolButton="['refresh', 'setting', 'exportCurrent', 'exportAll']"
+				:exportMethod="departmentOrderListExport"
+				:exportParams="requestParams"
+			>
+				<template #tableHeader="scope">
+					<el-button type="primary" @click="onJbExport" :disabled="!scope.isSelected" :loading="state.loading"
+						><SvgIcon name="iconfont icon-daochu" class="mr5" />交办单导出
+					</el-button>
+				</template>
+				<!--				<template #description>
+                  <el-popover :width="500" trigger="click">
+                    <template #reference>
+                      <el-button circle title="口径说明"><SvgIcon name="ele-QuestionFilled" /></el-button>
+                    </template>
+                    <el-descriptions title="" :column="2" border>
+                      <el-descriptions-item label="信件编号">工单编号</el-descriptions-item>
+                      <el-descriptions-item label="受理时间">工单信件时间</el-descriptions-item>
+                      <el-descriptions-item label="信件标题">工单标题</el-descriptions-item>
+                      <el-descriptions-item label="超期部门"> 工单流转目标超期的部门 </el-descriptions-item>
+                      <el-descriptions-item label="到期时间"> 工单到期时间 </el-descriptions-item>
+                      <el-descriptions-item label="超期天数"> 工单超期的天数 </el-descriptions-item>
+                      <el-descriptions-item label="办件状态" :span="2"> 未签收,办理中,会签中,特提中,退回信件 </el-descriptions-item>
+                    </el-descriptions>
+                  </el-popover>
+                </template>-->
+				<template #title="{ row }">
+					<order-detail :order="row" @updateList="queryList">{{ row.title }}</order-detail>
+				</template>
+				<!-- 表格操作 -->
+				<template #operation="{ row }">
+					<order-detail :order="row" @updateList="queryList" />
+				</template>
+			</ProTable>
+		</el-card>
+	</div>
+</template>
+<script setup lang="tsx" name="statisticsDepartmentDetailHandleList">
+import { onMounted, reactive, ref, defineAsyncComponent } from 'vue';
+import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
+import { departmentOrderList, departmentOrderListExport } from '@/api/statistics/department';
+import { formatDate } from '@/utils/formatTime';
+import { defaultDateTime, defaultTimeStartEnd, shortcuts } from '@/utils/constants';
+import { useRoute } from 'vue-router';
+import { listBaseData } from '@/api/judicial';
+import Other from '@/utils/other';
+import { exportJbOrder } from '@/api/business/order';
+import { downloadZip } from '@/utils/tools';
+
+// 引入组件
+const OrderDetail = defineAsyncComponent(() => import('@/components/OrderDetail/index.vue')); // 工单详情
+// 表格配置项
+const columns = ref<any[]>([
+	{ type: 'selection', fixed: 'left', width: 55, align: 'center' },
+	{ prop: 'statusText', label: '工单状态', width: 100 },
+	{ prop: 'sourceChannel', label: '来源方式', width: 120 },
+	{ prop: 'actualHandleStepName', label: '当前节点', width: 120 },
+	{ prop: 'no', label: '工单编码', width: 150 },
+	{
+		prop: 'startTime',
+		label: '受理时间',
+		minWidth: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.startTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'title', label: '工单标题', width: 300 },
+	{
+		prop: 'expiredTime',
+		label: '工单期满时间',
+		minWidth: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.expiredTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'actualHandleOrgName', label: '接办部门', minWidth: 150 },
+	{
+		prop: 'actualHandleTime',
+		label: '接办时间',
+		minWidth: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.actualHandleTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{
+		prop: 'filedTime',
+		label: '办结时间',
+		minWidth: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.filedTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'hotspotName', label: '热点分类', minWidth: 150 },
+	{ prop: 'acceptType', label: '受理类型', width: 150 },
+	{ prop: 'acceptorName', label: '受理人', width: 120 },
+	{ prop: 'operation', label: '操作', fixed: 'right', width: 100, align: 'center' },
+]);
+const searchCol = ref(true); // 展开/收起
+// 展开/收起
+const closeSearch = () => {
+	searchCol.value = !searchCol.value;
+};
+// 定义变量内容
+const ruleFormRef = ref<RefType>(); // 表单ref
+const state = reactive<any>({
+	queryParams: {
+		// 查询条件
+		PageIndex: 1,
+		PageSize: 10,
+		crTime: defaultDateTime,
+		StartTime: null,
+		EndTime: null,
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
+});
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+
+/** 获取列表 */
+const route = useRoute();
+const routeQueryParams = route.query;
+const requestParams = ref({});
+const queryList = () => {
+	state.loading = true;
+	requestParams.value = Other.deepClone(state.queryParams);
+	requestParams.value.StartTime = state.queryParams.crTime === null ? null : state.queryParams.crTime[0]; // 生成时间
+	requestParams.value.EndTime = state.queryParams.crTime === null ? null : state.queryParams.crTime[1];
+	Reflect.deleteProperty(requestParams.value, 'crTime'); // 删除无用的参数
+	departmentOrderList(requestParams.value)
+		.then((res: any) => {
+			state.tableData = res.result?.items ?? [];
+			state.total = res.result.total ?? 0;
+			state.loading = false;
+		})
+		.catch(() => {
+			state.loading = false;
+		});
+};
+/** 重置按钮操作 */
+const hotSpotRef = ref<RefType>();
+const resetQuery = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	hotSpotRef.value?.reset();
+	queryList();
+};
+// 回访详情
+const visitDetailRef = ref<RefType>();
+const visitDetail = (row: any) => {
+	visitDetailRef.value.openDialog({ ...row, id: row.visitId }, '回访详情');
+};
+// 获取查询条件基础信息
+const getBaseData = async () => {
+	try {
+		const res: any = await listBaseData();
+		const mappings: any = {
+			acceptTypeOptions: 'acceptTypeOptions',
+			channelOptions: 'channelOptions',
+			orgsOptions: 'orgsOptions',
+			pushTypeOptions: 'pushTypeOptions',
+			orderStatusOptions: 'orderStatusOptions',
+			identityTypeOptions: 'identityTypeOptions',
+			currentStepOptions: 'currentStepOptions',
+		};
+		for (const key in mappings) {
+			state[key] = res.result?.[mappings[key]] ?? [];
+		}
+	} catch (error) {
+		console.log(error);
+	}
+};
+// 交办单导出
+const proTableRef = ref<RefType>();
+const onJbExport = () => {
+	const ids = proTableRef.value.selectedList.map((item: any) => item.id);
+	ElMessageBox.confirm(`您确定导出选中的${proTableRef.value.selectedList.length}个工单的交办单,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			state.loading = true;
+			exportJbOrder(ids)
+				.then((res: any) => {
+					downloadZip(res);
+					state.loading = false;
+					ElMessage.success('导出成功');
+				})
+				.catch(() => {
+					state.loading = false;
+				});
+		})
+		.catch(() => {});
+};
+onMounted(() => {
+	getBaseData();
+	queryList();
+});
+</script>

+ 275 - 0
src/views/statistics/department/detailOverdueList.vue

@@ -0,0 +1,275 @@
+<template>
+	<div class="statistics-department-detail-overdue-list-container layout-pd">
+		<el-card shadow="never">
+			<div class="flex-center-align mb20">
+				<span class="fast-search-label">快捷查询</span>
+				<el-radio-group v-model="state.queryParams.ExpiredType" @change="handleQuery">
+					<el-radio-button label="1">系统中超期</el-radio-button>
+					<el-radio-button label="2">申请延期超期</el-radio-button>
+				</el-radio-group>
+			</div>
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent label-width="100px">
+				<el-row :gutter="0">
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label="部门名称" prop="OrgName">
+							<el-input v-model="state.queryParams.OrgName" placeholder="部门名称" clearable @keyup.enter="handleQuery" />
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label="工单编码" prop="No">
+							<el-input v-model="state.queryParams.No" placeholder="工单编码" clearable @keyup.enter="handleQuery" />
+						</el-form-item>
+					</el-col>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<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="handleQuery"
+								value-format="YYYY-MM-DD[T]HH:mm:ss"
+								:default-time="defaultTimeStartEnd"
+                :clearable="false"
+							/>
+						</el-form-item>
+					</el-col>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-show="!searchCol">
+							<el-form-item label="工单状态" prop="Statuses">
+								<el-select v-model="state.queryParams.Statuses" placeholder="请选择工单状态" clearable class="w100" multiple>
+									<el-option v-for="item in state.orderStatusOptions" :value="item.key" :key="item.key" :label="item.value" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
+						<el-form-item label=" ">
+							<div class="flex-end w100">
+								<el-button type="primary" @click="handleQuery" :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-form-item>
+					</el-col>
+				</el-row>
+			</el-form>
+		</el-card>
+		<el-card shadow="never">
+			<ProTable
+				ref="proTableRef"
+				:columns="columns"
+				:data="state.tableData"
+				@updateTable="queryList"
+				:loading="state.loading"
+				:total="state.total"
+				v-model:page-index="state.queryParams.PageIndex"
+				v-model:page-size="state.queryParams.PageSize"
+				:toolButton="['refresh', 'setting', 'exportCurrent', 'exportAll']"
+				:exportMethod="departmentOverdueListExport"
+				:exportParams="requestParams"
+			>
+				<template #tableHeader="scope">
+					<el-button type="primary" @click="onJbExport" :disabled="!scope.isSelected" :loading="state.loading"
+						><SvgIcon name="iconfont icon-daochu" class="mr5" />交办单导出
+					</el-button>
+				</template>
+				<!--				<template #description>
+					<el-popover :width="500" trigger="click">
+						<template #reference>
+							<el-button circle title="口径说明"><SvgIcon name="ele-QuestionFilled" /></el-button>
+						</template>
+						<el-descriptions title="" :column="2" border>
+							<el-descriptions-item label="信件编号">工单编号</el-descriptions-item>
+							<el-descriptions-item label="受理时间">工单信件时间</el-descriptions-item>
+							<el-descriptions-item label="信件标题">工单标题</el-descriptions-item>
+							<el-descriptions-item label="超期部门"> 工单流转目标超期的部门 </el-descriptions-item>
+							<el-descriptions-item label="到期时间"> 工单到期时间 </el-descriptions-item>
+							<el-descriptions-item label="超期天数"> 工单超期的天数 </el-descriptions-item>
+							<el-descriptions-item label="办件状态" :span="2"> 未签收,办理中,会签中,特提中,退回信件 </el-descriptions-item>
+						</el-descriptions>
+					</el-popover>
+				</template>-->
+				<template #title="{ row }">
+					<order-detail :order="row" @updateList="queryList">{{ row.title }}</order-detail>
+				</template>
+				<!-- 表格操作 -->
+				<template #operation="{ row }">
+					<order-detail :order="row" @updateList="queryList" />
+				</template>
+			</ProTable>
+		</el-card>
+	</div>
+</template>
+<script setup lang="tsx" name="statisticsDepartmentDetailOverdueList">
+import { onMounted, reactive, ref, defineAsyncComponent } from 'vue';
+import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
+import { departmentOverdueBase, departmentOverdueList, departmentOverdueListExport } from '@/api/statistics/department';
+import { formatDate } from '@/utils/formatTime';
+import {defaultDateTime, defaultTimeStartEnd, shortcuts} from '@/utils/constants';
+import { useRoute } from 'vue-router';
+import Other from '@/utils/other';
+import { exportJbOrder } from '@/api/business/order';
+import { downloadZip } from '@/utils/tools';
+
+// 引入组件
+const OrderDetail = defineAsyncComponent(() => import('@/components/OrderDetail/index.vue')); // 工单详情
+// 表格配置项
+const columns = ref<any[]>([
+	{ type: 'selection', fixed: 'left', width: 55, align: 'center' },
+	{ prop: 'no', label: '工单编码', width: 150 },
+  {
+    prop: 'creationTime',
+    label: '受理时间',
+    minWidth: 170,
+    render: (scope) => {
+      return <span>{formatDate(scope.row.creationTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+    },
+  },
+  { prop: 'title', label: '工单标题', width: 300 },
+  { prop: 'sourceChannel', label: '来源方式', width: 120 },
+  { prop: 'daysOverdueOrgName', label: '超期部门', width: 150 },
+  {
+    prop: 'filedTime',
+    label: '办结时间',
+    minWidth: 170,
+    render: (scope) => {
+      return <span>{formatDate(scope.row.filedTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+    },
+  },
+  {
+    prop: 'expiredTime',
+    label: '工单期满时间',
+    minWidth: 170,
+    render: (scope) => {
+      return <span>{formatDate(scope.row.expiredTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+    },
+  },
+
+	{ prop: 'overDays', label: '超期天数' },
+	{ prop: 'statusText', label: '工单状态', minWidth: 100 },
+	{ prop: 'operation', label: '操作', fixed: 'right', width: 100, align: 'center' },
+]);
+const searchCol = ref(true); // 展开/收起
+// 展开/收起
+const closeSearch = () => {
+	searchCol.value = !searchCol.value;
+};
+// 定义变量内容
+const ruleFormRef = ref<RefType>(); // 表单ref
+const state = reactive<any>({
+	queryParams: {
+		// 查询条件
+		PageIndex: 1,
+		PageSize: 10,
+		ExpiredType: '1',
+		crTime: defaultDateTime,
+    StartTime: null,
+    EndTime: null,
+    No:null,
+    Statuses: [], // 状态
+    OrgName:null, // 部门名称
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
+  orderStatusOptions:[],// 工单状态
+});
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+
+/** 获取列表 */
+const route = useRoute();
+const routeQueryParams = route.query;
+const requestParams = ref({});
+const queryList = () => {
+	state.loading = true;
+	requestParams.value = Other.deepClone(state.queryParams);
+	requestParams.value.StartTime = state.queryParams.crTime === null ? null : state.queryParams.crTime[0]; // 生成时间
+	requestParams.value.EndTime = state.queryParams.crTime === null ? null : state.queryParams.crTime[1];
+	Reflect.deleteProperty(requestParams.value, 'crTime'); // 删除无用的参数
+	departmentOverdueList(requestParams.value)
+		.then((res: any) => {
+			state.tableData = res.result?.items ?? [];
+			state.total = res.result.total ?? 0;
+			state.loading = false;
+		})
+		.catch(() => {
+			state.loading = false;
+		});
+};
+/** 重置按钮操作 */
+const hotSpotRef = ref<RefType>();
+const resetQuery = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+  state.queryParams.ExpiredType = '1';
+	queryList();
+};
+// 回访详情
+const visitDetailRef = ref<RefType>();
+const visitDetail = (row: any) => {
+	visitDetailRef.value.openDialog({ ...row, id: row.visitId }, '回访详情');
+};
+// 获取查询条件基础信息
+const getBaseData = async () => {
+	try {
+		const { result } = await departmentOverdueBase();
+		state.orderStatusOptions = result.orderStatusOptions ?? [];
+	} catch (error) {
+		console.log(error);
+	}
+};
+// 交办单导出
+const proTableRef = ref<RefType>();
+const onJbExport = () => {
+	const ids = proTableRef.value.selectedList.map((item: any) => item.id);
+	ElMessageBox.confirm(`您确定导出选中的${proTableRef.value.selectedList.length}个工单的交办单,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			state.loading = true;
+			exportJbOrder(ids)
+				.then((res: any) => {
+					downloadZip(res);
+					state.loading = false;
+					ElMessage.success('导出成功');
+				})
+				.catch(() => {
+					state.loading = false;
+				});
+		})
+		.catch(() => {});
+};
+onMounted(() => {
+	getBaseData();
+	queryList();
+});
+</script>
+<style scoped lang="scss">
+.statistics-department-detail-overdue-list-container {
+	.arrow {
+		transition: transform var(--el-transition-duration);
+		cursor: pointer;
+	}
+	.arrow.is-reverse {
+		transform: rotateZ(-180deg);
+	}
+}
+</style>

+ 7 - 0
src/views/statistics/department/handle.vue

@@ -28,6 +28,7 @@
 					<el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
 						<SvgIcon name="ele-Refresh" class="mr5" />重置
 					</el-button>
+					<el-button type="primary" @click="onDetailList" :loading="state.loading"> <SvgIcon name="ele-List" class="mr5" /> 列表明细 </el-button>
 				</el-form-item>
 			</el-form>
 		</el-card>
@@ -572,6 +573,12 @@ const linkDetail = (scope, key?: string) => {
 		});
 	}
 };
+// 跳转列表明细
+const onDetailList = () => {
+	router.push({
+		path: '/statistics/department/detailHandleList',
+	});
+};
 onMounted(() => {
 	queryList();
 });

+ 7 - 0
src/views/statistics/department/overdue.vue

@@ -25,6 +25,7 @@
 					<el-button @click="resetQuery(ruleFormRef)" class="default-button" :loading="state.loading">
 						<SvgIcon name="ele-Refresh" class="mr5" />重置
 					</el-button>
+          <el-button type="primary" @click="onDetailList" :loading="state.loading"> <SvgIcon name="ele-List" class="mr5" /> 列表明细 </el-button>
 				</el-form-item>
 			</el-form>
 		</el-card>
@@ -214,6 +215,12 @@ const linkDetail = (row: any, type: string) => {
 		},
 	});
 };
+// 跳转列表明细
+const onDetailList = () => {
+  router.push({
+    path: '/statistics/department/detailOverdueList',
+  });
+};
 onMounted(() => {
 	queryList();
 });

+ 28 - 26
src/views/statistics/order/detailSource.vue

@@ -46,6 +46,7 @@
 				show-summary
 				isSpecialExport
 				:key="Math.random()"
+				@updateColSetting="updateColSetting"
 			>
 			</ProTable>
 		</el-card>
@@ -98,13 +99,13 @@ const queryList = async () => {
 				label: item.value,
 				align: 'center',
 				minWidth: 90,
-        render: (scope) => {
-          return (
-              <el-button type="primary" link onClick={() => linkDetail(scope)}>
-                {scope.row[item.key]}
-              </el-button>
-          );
-        },
+				render: (scope) => {
+					return (
+						<el-button type="primary" link onClick={() => linkDetail(scope)}>
+							{scope.row[item.key]}
+						</el-button>
+					);
+				},
 			};
 		});
 		// 计算横轴小计
@@ -118,31 +119,32 @@ const queryList = async () => {
 				subtotal,
 			};
 		});
-		columns.value.unshift({
-			prop: 'Hour',
-			label: '日期',
-			align: 'center',
-			minWidth: 120,
-			fixed: 'left',
-		},{
-      prop: 'subtotal',
-      label: '小计',
-      align: 'center',
-      render: (scope) => {
-        return (
-            <el-button type="primary" link onClick={() => linkDetail(scope)}>
-              {scope.row.subtotal}
-            </el-button>
-        );
-      },
-    });
-    state.tableData = state.tableData.filter((item) => item.Hour !== '0');
+		columns.value.unshift(
+			{
+				prop: 'Hour',
+				label: '日期',
+				align: 'center',
+				minWidth: 120,
+				fixed: 'left',
+			},
+			{
+				prop: 'subtotal',
+				label: '小计',
+				align: 'center',
+			}
+		);
+		state.tableData = state.tableData.filter((item) => item.Hour !== '0');
 		state.loading = false;
 	} catch (e) {
 		state.loading = false;
 		console.log(e);
 	}
 };
+// 列设置更新 列更新后需要重新计算小计
+const updateColSetting = (exportNewColumns) => {
+    // 更新列设置后 需要计算展示的列的小计
+
+};
 /** 重置按钮操作 */
 const resetQuery = (formEl: FormInstance | undefined) => {
 	if (!formEl) return;

+ 26 - 3
src/views/system/user/component/User-add.vue

@@ -55,10 +55,27 @@
 								label: 'telNo',
 								value: 'telNo',
 							}"
-              clearable
+							clearable
 						/>
 					</el-form-item>
 				</el-col>
+				<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+					<el-form-item label="默认分机组" prop="defaultTelGroup" :rules="[{ required: false, message: '请选择默认分机组', trigger: 'change' }]">
+						<el-select-v2
+							v-model="state.ruleForm.defaultTelGroup"
+							:options="props.telsListGroup"
+							placeholder="请选择默认分机组"
+							filterable
+							class="w100"
+							:props="{
+								label: 'no',
+								value: 'no',
+							}"
+							clearable
+						>
+						</el-select-v2>
+					</el-form-item>
+				</el-col>
 				<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
 					<el-form-item label="邮箱" prop="email" :rules="[{ required: false, message: '请填写邮箱', trigger: 'blur' }]">
 						<el-input v-model="state.ruleForm.email" placeholder="请填写邮箱" clearable></el-input>
@@ -138,6 +155,11 @@ const props = defineProps<{
 		type: Array<any>;
 		default: () => [];
 	};
+	telsListGroup: {
+		// 分机组
+		type: Array<any>;
+		default: () => [];
+	};
 }>();
 // 定义变量内容
 const state = reactive<any>({
@@ -153,8 +175,9 @@ const state = reactive<any>({
 		userType: 0, // 用户类型
 		defaultTelNo: null, //默认分机
 		email: '', //邮箱
-    fullOrgName:null, // 部门全称
+		fullOrgName: null, // 部门全称
 		roleIds: <EmptyArrayType>[], // 角色
+		defaultTelGroup: null, // 默认分机组
 	},
 });
 let loading = ref<boolean>(false); // 确定按钮loading
@@ -176,7 +199,7 @@ const cascadeRef = ref<RefType>();
 const getKnowledgeList = () => {
 	let currentNode = cascadeRef.value.getCheckedNodes();
 	state.ruleForm.orgCode = currentNode[0]?.data.id ?? '';
-  state.ruleForm.fullOrgName = currentNode[0]?.text ?? '';
+	state.ruleForm.fullOrgName = currentNode[0]?.text ?? '';
 };
 
 // 新增

+ 22 - 0
src/views/system/user/component/User-edit.vue

@@ -61,6 +61,23 @@
 						/>
 					</el-form-item>
 				</el-col>
+				<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+					<el-form-item label="默认分机组" prop="defaultTelGroup" :rules="[{ required: false, message: '请选择默认分机组', trigger: 'change' }]">
+						<el-select-v2
+							v-model="state.ruleForm.defaultTelGroup"
+							:options="props.telsListGroup"
+							placeholder="请选择默认分机组"
+							filterable
+							class="w100"
+							:props="{
+								label: 'no',
+								value: 'no',
+							}"
+							clearable
+						>
+						</el-select-v2>
+					</el-form-item>
+				</el-col>
 				<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
 					<el-form-item label="邮箱" prop="email" :rules="[{ required: false, message: '请填写邮箱', trigger: 'blur' }]">
 						<el-input v-model="state.ruleForm.email" placeholder="请填写邮箱" clearable></el-input>
@@ -143,6 +160,11 @@ const props = defineProps<{
 		type: Array<any>;
 		default: () => [];
 	};
+	telsListGroup: {
+		// 分机组
+		type: Array<any>;
+		default: () => [];
+	};
 }>();
 // 定义变量内容
 const state = reactive<any>({

+ 44 - 24
src/views/system/user/index.vue

@@ -7,26 +7,26 @@
 					<el-scrollbar style="height: calc(100% - 40px);'" ref="scrollBarRef">
 						<el-skeleton :loading="state.loading" animated :rows="10">
 							<template #default>
-                <el-auto-resizer class="table">
-                  <template #default="{ height, width }">
-                    <el-tree-v2
-                      :data="state.orgData"
-                      :filter-method="filterNodeOrg"
-                      ref="treRef"
-                      highlight-current
-                      :expand-on-click-node="false"
-                      @node-click="handleNodeClick"
-                      :item-size="40"
-                      :height="height"
-                      empty-text="暂无组织数据"
-                      :props="{ label: 'name', value: 'id' }"
-                    >
-                      <template #default="{ node }">
-                        <span>{{ node.label }}</span>
-                      </template>
-                    </el-tree-v2>
-                  </template>
-                </el-auto-resizer>
+								<el-auto-resizer class="table">
+									<template #default="{ height, width }">
+										<el-tree-v2
+											:data="state.orgData"
+											:filter-method="filterNodeOrg"
+											ref="treRef"
+											highlight-current
+											:expand-on-click-node="false"
+											@node-click="handleNodeClick"
+											:item-size="40"
+											:height="height"
+											empty-text="暂无组织数据"
+											:props="{ label: 'name', value: 'id' }"
+										>
+											<template #default="{ node }">
+												<span>{{ node.label }}</span>
+											</template>
+										</el-tree-v2>
+									</template>
+								</el-auto-resizer>
 							</template>
 						</el-skeleton>
 					</el-scrollbar>
@@ -107,6 +107,7 @@
 			:baseOrg="state.orgData"
 			:baseData="state.baseData"
 			:telsList="state.telsList"
+			:telsListGroup="state.telsListGroup"
 		/>
 		<user-edit
 			ref="userEditRef"
@@ -115,6 +116,7 @@
 			:baseOrg="state.orgData"
 			:baseData="state.baseData"
 			:telsList="state.telsList"
+			:telsListGroup="state.telsListGroup"
 		/>
 	</div>
 </template>
@@ -125,9 +127,10 @@ import type { FormInstance } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { formatDate } from '@/utils/formatTime';
 import { delUser, getBaseData, getCanUseOrgByUser, getRoles, getUserListPaged, restPwd } from '@/api/system/user';
-import { getTelList } from '@/api/public/wex';
+import { getTelGroupList, getTelList, getTelListV2 } from '@/api/public/wex';
 import { Splitpanes, Pane } from 'splitpanes';
 import 'splitpanes/dist/splitpanes.css';
+import { getCurrentCityConfig } from '@/utils/appConfig';
 
 // 引入组件
 const UserAdd = defineAsyncComponent(() => import('@/views/system/user/component/User-add.vue')); // 新增用户组件
@@ -173,6 +176,7 @@ const state = reactive<any>({
 	roleOptions: [], // 角色数据
 	baseData: [], // 基础数据
 	telsList: [], // 分机数据
+	telsListGroup: [], //分机组
 });
 const ruleFormRef = ref<FormInstance>(); //表单ref
 const filterOrg = ref(''); // 搜索部门名称
@@ -238,9 +242,25 @@ const getBaseDataFn = () => {
 	});
 };
 // 获取分机列表
-const getTelsListFn = async (object?: object) => {
-	const res: any = await getTelList(object);
-	state.telsList = res?.result ?? [];
+const { cityName } = getCurrentCityConfig();
+const getTelsListFn = async () => {
+	let tels, telGroup;
+	if (import.meta.env.VITE_CURRENT_CITY === 'zigong') {
+		[tels, telGroup] = await Promise.all([getTelListV2(), getTelGroupList()]);
+		tels.result = tels.result.map((item: any) => {
+			return {
+				...item,
+				telNo: item.no,
+			};
+		});
+	} else if (import.meta.env.VITE_CURRENT_CITY === 'yibin') {
+		[tels, telGroup] = await Promise.all([getTelList()]);
+	} else {
+		[tels, telGroup] = await Promise.all([getTelList()]);
+	}
+	console.log(tels);
+	state.telsList = tels?.result ?? [];
+	state.telsListGroup = telGroup?.result ?? [];
 };
 // 点击节点
 const handleNodeClick = (data: any) => {

+ 37 - 0
src/views/todo/order/index.vue

@@ -180,6 +180,21 @@ const columnsTodo = [
 	{ prop: 'actualHandleStepName', label: '办理节点', width: 150 },
 	{ prop: 'statusText', label: '工单状态', width: 100 },
 	{ prop: 'title', label: '工单标题', width: 300 },
+/*  {
+    prop: 'isUrgentText',
+    label: '退回不通过原因',
+    render: (scope) => {
+      return <span class="color-danger font-bold">{scope.row.isUrgentText}</span>;
+    },
+  },
+  {
+    prop: 'creationTime',
+    label: '退回审批时间',
+    width: 170,
+    render: (scope) => {
+      return <span>{formatDate(scope.row.creationTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+    },
+  },*/
 	{ prop: 'counterSignTypeText', label: '是否会签', width: 100 },
 	{
 		prop: 'creationTime',
@@ -221,9 +236,31 @@ const columnsDone = [
 	{ prop: 'expiredStatusText', label: '超期状态', align: 'center', width: 80 },
 	{ prop: 'no', label: '工单编码', width: 150 },
 	{ prop: 'isProvinceText', label: '省/市工单', width: 100 },
+  {
+    prop: 'isUrgentText',
+    label: '是否紧急',
+    render: (scope) => {
+      return <span class="color-danger font-bold">{scope.row.isUrgentText}</span>;
+    },
+  },
 	{ prop: 'actualHandleStepName', label: '办理节点', width: 150 },
 	{ prop: 'statusText', label: '工单状态', width: 100 },
 	{ prop: 'title', label: '工单标题', width: 300 },
+/*  {
+    prop: 'isUrgentText',
+    label: '退回不通过原因',
+    render: (scope) => {
+      return <span class="color-danger font-bold">{scope.row.isUrgentText}</span>;
+    },
+  },
+  {
+    prop: 'creationTime',
+    label: '退回审批时间',
+    width: 170,
+    render: (scope) => {
+      return <span>{formatDate(scope.row.creationTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+    },
+  },*/
 	{ prop: 'counterSignTypeText', label: '是否会签', width: 100 },
 	{
 		prop: 'creationTime',