瀏覽代碼

Merge branch 'master' into yibin

# Conflicts:
#	.env.yibin
zhangchong 1 年之前
父節點
當前提交
ac95d63d9e
共有 100 個文件被更改,包括 2630 次插入1237 次删除
  1. 14 10
      .env.development
  2. 16 10
      .env.production
  3. 38 0
      .env.st
  4. 21 15
      .env.yibin
  5. 4 0
      package.json
  6. 87 4
      src/App.vue
  7. 0 0
      src/api/auxiliary/citizen.ts
  8. 10 0
      src/api/business/delay.ts
  9. 10 0
      src/api/business/discern.ts
  10. 12 1
      src/api/business/order.ts
  11. 6 27
      src/api/business/redo.ts
  12. 11 0
      src/api/business/special.ts
  13. 10 1
      src/api/business/visit.ts
  14. 29 19
      src/api/home/index.ts
  15. 187 0
      src/api/judicial/index.ts
  16. 12 0
      src/api/knowledge/index.ts
  17. 12 0
      src/api/login/index.ts
  18. 34 76
      src/api/public/file.ts
  19. 16 0
      src/api/public/log.ts
  20. 31 7
      src/api/query/countersign.ts
  21. 12 1
      src/api/smartVisit/index.ts
  22. 119 0
      src/api/statistics/center.ts
  23. 71 0
      src/api/statistics/department.ts
  24. 8 41
      src/api/statistics/order.ts
  25. 18 4
      src/api/system/roles.ts
  26. 10 0
      src/api/system/user.ts
  27. 159 0
      src/api/system/workforce.ts
  28. 12 0
      src/api/tels/extension.ts
  29. 二進制
      src/assets/images/login/bg.png
  30. 二進制
      src/assets/images/login/code1.png
  31. 二進制
      src/assets/images/login/code2.png
  32. 二進制
      src/assets/images/login/code3.png
  33. 二進制
      src/assets/images/login/code4.png
  34. 二進制
      src/assets/images/login/code5.png
  35. 3 3
      src/components/AnnexList/index.vue
  36. 5 4
      src/components/AudioPlayer/index.vue
  37. 3 9
      src/components/Hotspot/index.vue
  38. 1 1
      src/components/IconSelector/index.vue
  39. 0 82
      src/components/ImgVerify/hooks.ts
  40. 0 35
      src/components/ImgVerify/index.vue
  41. 0 4
      src/components/LogicFlow/index.vue
  42. 51 68
      src/components/OrderDetail/index.vue
  43. 5 4
      src/components/ProTable/components/Pagination.vue
  44. 27 26
      src/components/ProTable/index.vue
  45. 110 129
      src/components/ProcessAudit/index.vue
  46. 3 2
      src/components/ProcessTimeLine/index.vue
  47. 62 0
      src/components/TextTooltip/index.vue
  48. 0 169
      src/components/Upload/index.vue
  49. 1 1
      src/layout/component/main.vue
  50. 1 1
      src/layout/footer/footer.vue
  51. 8 8
      src/layout/footer/index.vue
  52. 1 1
      src/layout/main/columns.vue
  53. 2 2
      src/layout/main/defaults.vue
  54. 2 2
      src/layout/navBars/breadcrumb/search.vue
  55. 239 75
      src/layout/navBars/breadcrumb/telControl.vue
  56. 1 1
      src/layout/navBars/breadcrumb/user.vue
  57. 0 4
      src/layout/navBars/breadcrumb/userNews.vue
  58. 4 3
      src/layout/navBars/tagsView/tagsView.vue
  59. 5 4
      src/layout/navMenu/horizontal.vue
  60. 4 3
      src/layout/navMenu/subItem.vue
  61. 4 3
      src/layout/navMenu/subThreeItem.vue
  62. 3 2
      src/layout/navMenu/vertical.vue
  63. 4 4
      src/layout/routerView/parent.vue
  64. 4 3
      src/main.ts
  65. 7 1
      src/router/backEnd.ts
  66. 209 0
      src/router/route.ts
  67. 3 0
      src/stores/appConfig.ts
  68. 1 1
      src/stores/keepAliveNames.ts
  69. 1 1
      src/stores/themeConfig.ts
  70. 11 8
      src/stores/userInfo.ts
  71. 24 2
      src/theme/element.scss
  72. 2 0
      src/theme/splitpanes.scss
  73. 2 2
      src/types/layout.d.ts
  74. 1 0
      src/types/mitt.d.ts
  75. 4 0
      src/types/pinia.d.ts
  76. 29 28
      src/utils/checkUpdate.tsx
  77. 24 8
      src/utils/constants.ts
  78. 37 0
      src/utils/echarts.ts
  79. 55 17
      src/utils/formatTime.ts
  80. 1 1
      src/utils/ola_api.ts
  81. 7 1
      src/utils/request.ts
  82. 58 19
      src/utils/signalR.tsx
  83. 70 40
      src/utils/tools.ts
  84. 9 10
      src/views/auxiliary/advice/index.vue
  85. 10 5
      src/views/auxiliary/businessTag/index.vue
  86. 1 1
      src/views/auxiliary/citizen/components/Tags-edit.vue
  87. 109 0
      src/views/auxiliary/citizen/components/Tags-record.vue
  88. 17 32
      src/views/auxiliary/citizen/index.vue
  89. 10 5
      src/views/auxiliary/knowledgeLexicon/index.vue
  90. 9 3
      src/views/auxiliary/message/index.vue
  91. 7 2
      src/views/auxiliary/msgTemplate/index.vue
  92. 0 4
      src/views/auxiliary/notice/detail.vue
  93. 36 31
      src/views/auxiliary/notice/index.vue
  94. 1 1
      src/views/auxiliary/orderLexicon/components/Order-lexicon-add.vue
  95. 1 1
      src/views/auxiliary/orderLexicon/components/Order-lexicon-edit.vue
  96. 10 5
      src/views/auxiliary/orderLexicon/index.vue
  97. 0 102
      src/views/business/citizen/components/Tags-record.vue
  98. 310 0
      src/views/business/countersign/index.vue
  99. 20 24
      src/views/business/delay/audit.vue
  100. 12 13
      src/views/business/delay/components/Delay-detail.vue

+ 14 - 10
.env.development

@@ -1,35 +1,39 @@
-# 本地环境
+# 开发环境
 VITE_MODE_NAME=development
 
 # 防止部署多套系统到同一域名不同目录时,变量共用的问题 设置不同的前缀
 VITE_STORAGE_NAME=hotline
 
-# # 基础请求地址
+# 基础请求地址
 VITE_API_URL=http://110.188.24.28:50100
 
-# #socket API
+# socket API
 VITE_API_SOCKET_URL=http://110.188.24.28:50100/hubs/hotline
 
-# #上传 API
+# 上传 API
 VITE_API_UPLOAD_URL=http://110.188.24.28:50120
 
-# # 文件上传地址前缀
+# 文件上传地址前缀
 VITE_FILE_PREFIX=http://open.fs.12345lm.cn
 
-# #高德地图安全密钥
+# 高德地图安全密钥
 VITE_AMAP_SECURITYJSCODE=dd12ddafb11921dbcdc5b9c4484bb4e2
 
-# #高德地图KEY
+# 高德地图KEY
 VITE_AMAP_KEY=83f51df235e4008e4eaf515cff63785c
 
-# # 呼叫中心socket地址
+# 呼叫中心socket地址
 VITE_CALLCENTER_SOCKET_URL=ws://222.213.23.229:29003/ola_socket
 
-# # 智能客服登录地址
+# 智能客服登录地址
 VITE_VOICE_ASSISTANT_API_URL=http://182.151.41.101:19081
 
-# # 智能客服socket地址
+# 智能客服socket地址
 VITE_VOICE_ASSISTANT_SOCKET_URL=ws://182.151.41.101:19005
 
+# 录音播放地址前缀
+VITE_RECORD_PREFIX=http://222.213.23.229:10085
 
+# 录音下载地址前缀
+VITE_RECORD_DOWNLOAD_PREFIX=http://222.213.23.229:10085
 

+ 16 - 10
.env.production

@@ -1,32 +1,38 @@
-# 线上环境
+# 测试环境
 VITE_MODE_NAME=production
 
 # 防止部署多套系统到同一域名不同目录时,变量共用的问题 设置不同的前缀
 VITE_STORAGE_NAME=hotline
 
-# # 基础请求地址
+# 基础请求地址
 VITE_API_URL=http://110.188.24.28:50100
 
-# #socket API
+# socket API
 VITE_API_SOCKET_URL=http://110.188.24.28:50100/hubs/hotline
 
-# #上传 API
+# 上传 API
 VITE_API_UPLOAD_URL=http://110.188.24.28:50120
 
-# # 文件上传地址前缀
+# 文件上传地址前缀
 VITE_FILE_PREFIX=http://open.fs.12345lm.cn
 
-# #高德地图安全密钥
+# 高德地图安全密钥
 VITE_AMAP_SECURITYJSCODE=dd12ddafb11921dbcdc5b9c4484bb4e2
 
-# #高德地图KEY
+# 高德地图KEY
 VITE_AMAP_KEY=83f51df235e4008e4eaf515cff63785c
 
-# # 呼叫中心socket地址
+# 呼叫中心socket地址
 VITE_CALLCENTER_SOCKET_URL=ws://222.213.23.229:29003/ola_socket
 
-# # 智能客服登录地址
+# 智能客服登录地址
 VITE_VOICE_ASSISTANT_API_URL=http://182.151.41.101:19081
 
-# # 智能客服socket地址
+# 智能客服socket地址
 VITE_VOICE_ASSISTANT_SOCKET_URL=ws://182.151.41.101:19005
+
+# 录音地址前缀
+VITE_RECORD_PREFIX=http://222.213.23.229:10085
+
+# 录音下载地址前缀
+VITE_RECORD_DOWNLOAD_PREFIX=http://222.213.23.229:10085

+ 38 - 0
.env.st

@@ -0,0 +1,38 @@
+# st
+VITE_MODE_NAME=st
+
+# 防止部署多套系统到同一域名不同目录时,变量共用的问题 设置不同的前缀
+VITE_STORAGE_NAME=hotline
+
+# 基础请求地址
+VITE_API_URL=http://110.188.24.28:50300
+
+# socket API
+VITE_API_SOCKET_URL=http://110.188.24.28:50300/hubs/hotline
+
+# 上传 API
+VITE_API_UPLOAD_URL=http://110.188.24.28:50120
+
+# 文件上传地址前缀
+VITE_FILE_PREFIX=http://open.fs.12345lm.cn
+
+# 高德地图安全密钥
+VITE_AMAP_SECURITYJSCODE=dd12ddafb11921dbcdc5b9c4484bb4e2
+
+# 高德地图KEY
+VITE_AMAP_KEY=83f51df235e4008e4eaf515cff63785c
+
+# 呼叫中心socket地址
+VITE_CALLCENTER_SOCKET_URL=ws://222.213.23.229:29003/ola_socket
+
+# 智能客服登录地址
+VITE_VOICE_ASSISTANT_API_URL=http://182.151.41.101:19081
+
+# 智能客服socket地址
+VITE_VOICE_ASSISTANT_SOCKET_URL=ws://182.151.41.101:19005
+
+# 录音地址前缀
+VITE_RECORD_PREFIX=http://222.213.23.229:10085
+
+# 录音下载地址前缀
+VITE_RECORD_DOWNLOAD_PREFIX=http://222.213.23.229:10085

+ 21 - 15
.env.yibin

@@ -1,32 +1,38 @@
-# # 宜宾环境
+# 宜宾环境
 VITE_MODE_NAME=yibin
 
-# # 基础请求地址
-VITE_API_URL=http://218.6.151.146:50102
-
 # 防止部署多套系统到同一域名不同目录时,变量共用的问题 设置不同的前缀
 VITE_STORAGE_NAME=hotline
 
-# #socket API
+# 基础请求地址
+VITE_API_URL=http://218.6.151.146:50102
+
+# socket API
 VITE_API_SOCKET_URL=http://218.6.151.146:50100/hubs/hotline
 
 # #上传 API
-VITE_API_UPLOAD_URL=http://218.6.151.146:50102
+VITE_API_UPLOAD_URL=http://218.6.151.146:50106
 
 # # 文件上传地址前缀
-VITE_FILE_PREFIX=http://open.fs.12345lm.cn
+VITE_FILE_PREFIX=http://218.6.151.146:50106
 
-# #高德地图安全密钥
+# 高德地图安全密钥
 VITE_AMAP_SECURITYJSCODE=dd12ddafb11921dbcdc5b9c4484bb4e2
 
-# #高德地图KEY
+# 高德地图KEY
 VITE_AMAP_KEY=83f51df235e4008e4eaf515cff63785c
 
-# # 呼叫中心socket地址
-VITE_CALLCENTER_SOCKET_URL=ws://222.213.23.229:29003/ola_socket
+# 呼叫中心socket地址
+VITE_CALLCENTER_SOCKET_URL=ws://218.6.151.146:50104/ola_socket
+
+# 智能客服登录地址
+VITE_VOICE_ASSISTANT_API_URL=http://218.6.151.146:50107
+
+# 智能客服socket地址
+VITE_VOICE_ASSISTANT_SOCKET_URL=ws://218.6.151.146:50108
 
-# # 智能客服登录地址
-VITE_VOICE_ASSISTANT_API_URL=http://182.151.41.101:19081
+# 录地址前缀
+VITE_RECORD_PREFIX=http://218.6.151.146:50104
 
-# # 智能客服socket地址
-VITE_VOICE_ASSISTANT_SOCKET_URL=ws://182.151.41.101:19005
+# 录音下载地址前缀
+VITE_RECORD_DOWNLOAD_PREFIX=http://192.168.2.212:29003

+ 4 - 0
package.json

@@ -8,6 +8,7 @@
 		"dev": "vite --force",
 		"build": "vite build",
 		"build:yibin": "vite build --mode yibin",
+		"build:st": "vite build --mode st",
 		"preview": "vite preview",
 		"serve": "http-server ./dist",
 		"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
@@ -23,6 +24,7 @@
 		"@wangeditor/editor-for-vue": "^5.1.12",
 		"axios": "^1.4.0",
 		"dayjs": "^1.11.9",
+		"echarts": "^5.5.0",
 		"element-plus": "~2.4.4",
 		"file-saver": "^2.0.5",
 		"html-docx-js-typescript": "^0.1.5",
@@ -39,7 +41,9 @@
 		"sortablejs": "^1.15.0",
 		"splitpanes": "^3.1.5",
 		"vue": "^3.2.45",
+		"vue-echarts": "^6.6.9",
 		"vue-router": "^4.1.6",
+		"vue3-puzzle-vcode": "^1.1.7",
 		"vue3-seamless-scroll": "^2.0.1",
 		"vuedraggable": "^4.1.0",
 		"webpack": "^5.0.0"

+ 87 - 4
src/App.vue

@@ -19,9 +19,13 @@ import { useThemeConfig } from '@/stores/themeConfig';
 import other from '@/utils/other';
 import checkUpdate from '@/utils/checkUpdate';
 import mittBus from '@/utils/mitt';
-import { Session, Local } from '@/utils/storage';
+import { Session, Local, Cookie } from '@/utils/storage';
 import setIntroduction from '@/utils/setIconfont';
+import { loginPageInfo } from '@/api/login';
+import { getImageUrl } from '@/utils/tools';
 import { useKeepALiveNames } from '@/stores/keepAliveNames';
+import { ola } from '@/utils/ola_api';
+import signalR from '@/utils/signalR';
 import { useUserInfo } from '@/stores/userInfo';
 // 引入组件
 const LockScreen = defineAsyncComponent(() => import('@/layout/lockScreen/index.vue'));
@@ -47,8 +51,6 @@ const setLockScreen = computed(() => {
 
 // 水印字符串
 const watermarkText = computed(() => {
-	// 防止锁屏后,刷新出现不相关界面
-	// https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
 	return themeConfig.value.watermarkText;
 });
 
@@ -71,13 +73,26 @@ const openSetTingsDrawer = () => {
 };
 // 设置初始化,防止刷新时恢复默认
 onBeforeMount(async () => {
+	// 获取登录页的背景图和系统名称等
+	const res: any = await loginPageInfo();
+	const globalTitle = res.result.sysName.join('|') ?? ''; // 标题名称
+	const loginImage = res.result.loginImage ? `url${res.result.loginImage}` : `url(${getImageUrl('login/bg.png')})`; // 登录页背景图
+  const isLoginMessageCode = res.result.isLoginMessageCode; // 是否开启短信验证码
+  storesThemeConfig.setThemeConfig(Object.assign(themeConfig.value, { globalTitle, loginImage, isLoginMessageCode }));
 	// 设置批量第三方 icon 图标
 	setIntroduction.cssCdn();
 	// 设置批量第三方 js
 	setIntroduction.jsCdn();
 });
+const unloadHandler = (e: BeforeUnloadEvent) => {
+	//发送消息
+	/*if (ola.ws) {
+		ola.logout();
+	}*/
+};
 // 页面加载时
 onMounted(() => {
+	window.addEventListener('beforeunload', (e) => unloadHandler(e));
 	nextTick(async () => {
 		try {
 			// 获取缓存中的布局配置
@@ -110,7 +125,7 @@ onMounted(() => {
 				event.stopPropagation();
 			};
 			/*mittBus.on('*', (index, data) => {
-      	console.log(index, data);
+        console.log(index, data);
       });*/
 		} catch (error) {
 			console.log(error);
@@ -133,6 +148,7 @@ const clearCacheTagsView = async (routeName: string) => {
 onUnmounted(() => {
 	mittBus.off('openSetTingsDrawer', () => {});
 	mittBus.off('clearCache', () => {});
+	window.removeEventListener('unload', (e) => unloadHandler(e));
 });
 // 监听路由的变化,设置网站标题
 watch(
@@ -144,4 +160,71 @@ watch(
 		deep: true,
 	}
 );
+const times = () => {
+  const performance = window.performance;
+  if (performance) {
+    setTimeout(() => { //异步获取, 同步获取时duration等值可能获取不到
+      const pnt: any = performance.getEntriesByType('navigation')[0]
+      const column = [
+        {
+          key: 'Redirect',
+          desc: '网页重定向的耗时',
+          value: pnt.redirectEnd - pnt.redirectStart,
+        },
+        {
+          key: 'AppCache',
+          desc: '检查本地缓存的耗时',
+          value: pnt.domainLookupStart - pnt.fetchStart,
+        },
+        {
+          key: 'DNS',
+          desc: 'DNS查询的耗时',
+          value: pnt.domainLookupEnd - pnt.domainLookupStart,
+        },
+        {
+          key: 'TCP',
+          desc: 'TCP连接的耗时',
+          value: pnt.connectEnd - pnt.connectStart,
+        },
+        {
+          key: 'Waiting(TTFB)',
+          desc: '从客户端发起请求到接收到响应的时间 / Time To First Byte',
+          value: pnt.responseStart - pnt.fetchStart,
+        },
+        {
+          key: '白屏时间',
+          desc: '首次渲染时间/白屏时间',
+          value: pnt.responseStart - pnt.startTime,
+        },
+        {
+          key: 'Content Download',
+          desc: '下载服务端返回数据的时间',
+          value: pnt.responseEnd - pnt.responseStart,
+        },
+        {
+          key: 'request',
+          desc: 'request请求耗时',
+          value: pnt.responseEnd - pnt.requestStart,
+        },
+        {
+          key: 'dom树',
+          desc: '解析dom树耗时',
+          value: pnt.domComplete - pnt.domInteractive,
+        },
+        {
+          key: 'DOMContentLoaded',
+          desc: 'dom加载完成的时间',
+          value: pnt.domContentLoadedEventEnd,
+        },
+        {
+          key: 'Loaded',
+          desc: '页面load的总耗时',
+          value: pnt.duration
+        },
+      ];
+      console && console.table && console.table(column);
+    }, 0)
+  }
+}
+// window.addEventListener('load', times); // onload 事件触发
 </script>

+ 0 - 0
src/api/business/citizen.ts → src/api/auxiliary/citizen.ts


+ 10 - 0
src/api/business/delay.ts

@@ -65,6 +65,16 @@ export const workflowDelayParams= () => {
         method: 'get'
     });
 };
+/**
+ * @description 延期审批参数
+ * @param {string} workflowId
+ */
+export const delayApproveParams = (workflowId:string) => {
+    return request({
+        url: `/api/v1/Order/delay/${workflowId}/nextsteps`,
+        method: 'get'
+    });
+}
 /**
  * @description 延期修改
  * @param {object} data

+ 10 - 0
src/api/business/discern.ts

@@ -64,6 +64,16 @@ export const workflowDiscernParams = () => {
 		method: 'get',
 	});
 };
+/**
+ * @description 甄别审批参数
+ * @param {string} workflowId
+ */
+export const discernApproveParams = (workflowId:string) => {
+	return request({
+		url: `/api/v1/Order/screen/${workflowId}/nextsteps`,
+		method: 'get'
+	});
+}
 /**
  * @description 甄别修改
  * @param {object} data

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

@@ -23,7 +23,7 @@ export const orderList = (params: any) => {
 		url: `/api/v1/Order`,
 		method: 'get',
 		params,
-		paramsSerializer: params => qs.stringify(params)
+		paramsSerializer: (params:any) => qs.stringify(params)
 	});
 };
 /**
@@ -208,4 +208,15 @@ export const orderHandle = (data: object) => {
 		method: 'post',
 		data
 	});
+}
+/**
+ * @description 省退回批量申请
+ * @param {object} data
+ */
+export const provinceReturn = (data: object) => {
+	return request({
+		url: `/api/v1/Order/send_back/batch`,
+		method: 'post',
+		data
+	});
 }

+ 6 - 27
src/api/business/redo.ts

@@ -3,43 +3,22 @@
  * @description 业务管理-工单重办
  */
 import request from '@/utils/request';
-/**
- * @description 工单重办列表
- * @param {object} params
- */
-export const redoList = (params: object) => {
-	return request({
-		url: `/api/v1/Order/redo`,
-		method: 'get',
-		params,
-	});
-};
-/**
- * @description 工单重办详情
- * @param id
- */
-export const redoDetail = (id: string) => {
-	return request({
-		url: `/api/v1/Order/redo/${id}`,
-		method: 'get',
-	});
-};
 /**
  * @description 工单重办基础信息
  */
-export const redoBaseData = () => {
+export const redoBaseData = (id: string) => {
 	return request({
-		url: `/api/v1/Order/base-data-redo`,
+		url: `/api/v1/Order/reTransact/base/${id}`,
 		method: 'get',
 	});
 };
 /**
- * @description 发起重办
- * @param {object} data
+ * @description 工单重办
+ * @param data 工单重办数据
  */
-export const redoApply = (data: object) => {
+export const redo = (data: any) => {
 	return request({
-		url: `/api/v1/Order/redo`,
+		url: '/api/v1/Order/re_transact',
 		method: 'post',
 		data,
 	});

+ 11 - 0
src/api/business/special.ts

@@ -66,4 +66,15 @@ export const specialDetail = (id: string) => {
         url: `/api/v1/Order/special/${id}`,
         method: 'get',
     });
+}
+/**
+ * @description 特提列表
+ * @param {object} params
+ */
+export const specialListAll = (params: object) => {
+    return request({
+        url: `/api/v1/Order/special/getspeciallist`,
+        method: 'get',
+        params
+    });
 }

+ 10 - 1
src/api/business/visit.ts

@@ -46,4 +46,13 @@ export const visitDetailList = (params: object) => {
         method: 'get',
         params
     });
-};
+};
+/**
+ * @description 回访查询基础数据
+ */
+export const visitSearchBaseData = () => {
+    return request({
+        url: `/api/v1/Order/visit/basedata`,
+        method: 'get'
+    });
+}

+ 29 - 19
src/api/home/index.ts

@@ -9,10 +9,10 @@ import request from '@/utils/request';
  * @return {*}
  */
 export const geFastMenu = () => {
-    return request({
-        url: '/api/v1/Home/get-myfastmenu',
-        method: 'get',
-    });
+	return request({
+		url: '/api/v1/Home/get-myfastmenu',
+		method: 'get',
+	});
 };
 /**
  * @description 获取可选快捷入口
@@ -20,11 +20,11 @@ export const geFastMenu = () => {
  * @return {*}
  */
 export const fastMenu = (data?: object) => {
-    return request({
-        url: '/api/v1/Home/get-fastmenu',
-        method: 'post',
-        data
-    });
+	return request({
+		url: '/api/v1/Home/get-fastmenu',
+		method: 'post',
+		data,
+	});
 };
 /**
  * @description 设置快捷入口
@@ -32,19 +32,29 @@ export const fastMenu = (data?: object) => {
  * @return {*}
  */
 export const setFastMenu = (data: object) => {
-    return request({
-        url: '/api/v1/Home/set-fastmenu',
-        method: 'post',
-        data
-    });
+	return request({
+		url: '/api/v1/Home/set-fastmenu',
+		method: 'post',
+		data,
+	});
 };
 /**
  * @description 获取首页数据
  * @return {*}
  */
 export const getHomeData = () => {
-    return request({
-        url: '/api/v1/CommonP/home_data',
-        method: 'get'
-    });
-}
+	return request({
+		url: '/api/v1/CommonP/home_data',
+		method: 'get',
+	});
+};
+/**
+ * @description 获取首页列表待办
+ * @return {*}
+ */
+export const getHomeList = () => {
+	return request({
+		url: '/api/v1/Order/waited/home',
+		method: 'get',
+	});
+};

+ 187 - 0
src/api/judicial/index.ts

@@ -0,0 +1,187 @@
+/*
+ * @Author: zc
+ * @description 司法行政监督
+ */
+import request from '@/utils/request';
+import qs from 'qs';
+
+/**
+ * @description 获取工单列表
+ * @param params
+ * @return {*}
+ */
+export const getWorkList = (params: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/getorderlist',
+		method: 'get',
+		params,
+		paramsSerializer: (params: any) => qs.stringify(params),
+	});
+};
+
+/**
+ * @description 列表页基础信息
+ * @param params
+ * @return {*}
+ */
+export const listBaseData = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/base-data',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 线索核实
+ * @param data
+ * @return {*}
+ */
+export const getClues = (data: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/clue_verification',
+		method: 'post',
+		data,
+	});
+};
+/**
+ * @description 线索核实基础信息
+ * @return {*}
+ */
+export const getCluesBaseData = () => {
+	return request({
+		url: '/api/v1/EnforcementOrder/treelist',
+		method: 'get',
+	});
+};
+/**
+ * @description 事项分类统计
+ * @param params
+ * @return {*}
+ */
+export const eventStatistics = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/event_classification_statistics',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 事项分类统计明细
+ * @param params
+ * @return {*}
+ */
+export const eventStatisticsDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/event_classification_statistics_order_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 区域排行统计
+ * @param params
+ * @return {*}
+ */
+export const areaStatistics = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/regional_classification_statistics',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 区域排行统计明细
+ * @param params
+ * @return {*}
+ */
+export const areaStatisticsDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/regional_classification_statistics_order_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 执法部门统计
+ * @param params
+ * @return {*}
+ */
+export const departmentStatistics = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_departmental_processing_statistics',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 执法部门统计明细
+ * @param params
+ * @return {*}
+ */
+export const departmentStatisticsDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_departmental_processing_statistics_order_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 执法部门统计部门明细
+ * @param params
+ * @return {*}
+ */
+export const departmentStatisticsDepartmentDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_departmental_processing_statistics_child',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 部门满意度统计
+ * @param params
+ * @return {*}
+ */
+export const departmentSatisfaction = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_visit_org_satisfaction_statistics',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 部门满意度统计明细
+ * @param params
+ * @return {*}
+ */
+export const departmentSatisfactionDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_visit_org_satisfaction_statistics_order_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 部门满意度统计子集明细
+ * @param params
+ * @return {*}
+ */
+export const departmentSatisfactionChildDetail = (params?: object) => {
+	return request({
+		url: '/api/v1/EnforcementOrder/enforcement_visit_org_satisfaction_statistics_child',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 获取区域信息
+ * @param params
+ * @return {*}
+ */
+export const getArea = (params?: object) => {
+	return request({
+		url: '/api/v1/DataScreen/get_system_area',
+		method: 'get',
+		params,
+	});
+};

+ 12 - 0
src/api/knowledge/index.ts

@@ -212,3 +212,15 @@ export const knowledgeTitle = (params: object) => {
 		params,
 	});
 };
+/**
+ * @description  获取知识库审批信息
+ * @param {object} params
+ * @return {*}
+ */
+export const knowledgeApproval = (params: object) => {
+	return request({
+		url: `/api/v1/Knowledge/audit_log`,
+		method: 'get',
+		params
+	});
+}

+ 12 - 0
src/api/login/index.ts

@@ -46,4 +46,16 @@ export const sendCode = (UserName: string) => {
 		url: `/api/v1/PushMessage/send_login_message_code?UserName=${UserName}`,
 		method: 'get'
 	});
+}
+/**
+ * @description 验证账号是否白名单
+ * @param {string} params  登录参数
+ *  @returns
+ */
+export const whiteList = (params:object) => {
+	return request({
+		url: '/api/v1/PushMessage/get_verify_whitelist',
+		method: 'get',
+		params
+	});
 }

+ 34 - 76
src/api/public/file.ts

@@ -1,93 +1,51 @@
 /*
  * @Author: zc
- * @description 附件管理
+ * @description 文件管理 导入 导出
  */
 import request from '@/utils/request';
-/**
- * @description 附件列表
- * @param {object}  params
- */
-export const fileList = (params: object) => {
-    return request({
-        url: `/api/v1/File/list`,
-        method: 'get',
-        params,
-    });
-};
-/**
- * @description 新增附件
- * @param {object}  data
- */
-export const fileAdd = (data: object) => {
-    return request({
-        url: `/api/v1/File`,
-        method: 'post',
-        data,
-    });
-};
-/**
- * @description 删除附件
- * @param {object}  data
- */
-export const fileDelete = (data: object) => {
-    return request({
-        url: `/api/v1/File`,
-        method: 'delete',
-        data,
-    });
-};
-/**
- * @description 更新附件
- * @param {object}  data
- */
-export const fileUpdate = (data: object) => {
-    return request({
-        url: `/api/v1/File`,
-        method: 'put',
-        data,
-    });
-};
-/**
- * @description 附件详情
- * @param {string}  id
- */
-export const fileDetail = (id: string) => {
-    return request({
-        url: `/${id}`,
-        method: 'get',
-    });
-};
 /**
  * @description 获取模板类型
  */
 export const fileTemplateType = () => {
-    return request({
-        url: `/api/v1/Order/import-basedata`,
-        method: 'get',
-    });
-}
+	return request({
+		url: `/api/v1/Order/import-basedata`,
+		method: 'get',
+	});
+};
 /**
  * @description 下载导入模板
  */
 export const fileTemplateDownload = () => {
-    return request({
-        url: `/api/v1/Order/download-order-template`,
-        method: 'get',
-        responseType: 'blob',
-        headers: { 'content-type': 'audio/mpeg' },
-    });
-}
+	return request({
+		url: `/api/v1/Order/download-order-template`,
+		method: 'get',
+		responseType: 'blob',
+		headers: { 'content-type': 'audio/mpeg' },
+	});
+};
 /**
  * @description 导入
  * @param {object}  data
  */
 export const fileImport = (data: object) => {
-    return request({
-        url: `/api/v1/Order/import-order`,
-        method: 'post',
-        headers: {
-            'Content-Type':'multipart/form-data'
-        },
-        data,
-    });
-}
+	return request({
+		url: `/api/v1/Order/import-order`,
+		method: 'post',
+		headers: {
+			'Content-Type': 'multipart/form-data',
+		},
+		data,
+	});
+};
+/**
+ * @description 下载文件
+ * @param {object}  params
+ */
+export const fileDownload = (params: object) => {
+	return request({
+		url: `/api/v1/File/download-proxy`,
+		method: 'get',
+		responseType: 'blob',
+		params,
+	});
+};

+ 16 - 0
src/api/public/log.ts

@@ -0,0 +1,16 @@
+/*
+ * @Author: zc
+ * @description 提交日志
+ */
+import request from '@/utils/request';
+/**
+ * @description 提交日志
+ * @param {object}  data
+ */
+export const submitLog = (data: object) => {
+  return request({
+    url: `/api/v1/Sys/log`,
+    method: 'post',
+    data,
+  });
+};

+ 31 - 7
src/api/query/countersign.ts

@@ -3,14 +3,38 @@
  * @description 业务查询-会签查询
  */
 import request from '@/utils/request';
+import qs from 'qs';
 /**
  * @description 会签列表查询
  * @param {object} params
  */
-export const countersignList = (params:object) => {
-    return request({
-        url: `/api/v1/Workflow/countersign`,
-        method: 'get',
-        params
-    });
-}
+export const countersignList = (params: object) => {
+	return request({
+		url: `/api/v1/Workflow/countersign`,
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 会签列表
+ * @param {object} params
+ */
+export const countersignData = (params: object) => {
+	return request({
+		url: `/api/v1/Workflow/order-countersign`,
+		method: 'get',
+		params,
+      paramsSerializer: (params: any) => qs.stringify(params),
+	});
+};
+/**
+ * @description 会签列表基础信息
+ * @param {object} params
+ */
+export const countersignBase = (params?: object) => {
+	return request({
+		url: `/api/v1/Workflow/order-countersign-base-data`,
+		method: 'get',
+		params
+	});
+};

+ 12 - 1
src/api/smartVisit/index.ts

@@ -3,6 +3,7 @@
  * @description 数据统计-话务统计
  */
 import request from '@/utils/request';
+import qs from "qs";
 /**
  * @description 智能回访列表
  * @param {object} params
@@ -33,7 +34,8 @@ export const  getSmartVisitRecord = (params:object) => {
     return request({
         url: `/api/v1/Ai/aivisit/canaivisit-list`,
         method: 'get',
-        params
+        params,
+        paramsSerializer: (params:any) => qs.stringify(params)
     });
 }
 /**
@@ -47,3 +49,12 @@ export const  smartVisitAdd = (data:object) => {
         data
     });
 }
+/**
+ * @description  新增智能回访任务 基础数据
+ */
+export const  smartVisitBaseData = () => {
+    return request({
+        url: `/api/v1/Ai/aivisit/base-data`,
+        method: 'get',
+    });
+}

+ 119 - 0
src/api/statistics/center.ts

@@ -0,0 +1,119 @@
+/*
+ * @Author: zc
+ * @description 数据统计-中心统计
+ */
+import request from '@/utils/request';
+import qs from "qs";
+/**
+ * @description 中心报表统计
+ * @param {object} params
+ */
+export const centerReport = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/center_report_forms_statistics`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 高频来电统计
+ * @param {object} params
+ */
+export const departmentHighFrequency = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/high_frequency_call_statistics`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 高频来电统计详情
+ * @param {object} params
+ */
+export const departmentHighFrequencyDetail = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/high_frequency_call_statistics_order_list`,
+    method: 'get',
+    params,
+    paramsSerializer: (params:any) => qs.stringify(params)
+  });
+}
+/**
+ * @description 高频事件统计
+ * @param {object} params
+ */
+export const departmentHighFrequencyEvent = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/highmatter-warning`,
+    method: 'get',
+    params,
+    paramsSerializer: (params:any) => qs.stringify(params)
+  });
+}
+/**
+ * @description 高频事件统计明细
+ * @param {object} params
+ */
+export const departmentHighFrequencyEventDetail = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/highmatter-warning-detail`,
+    method: 'get',
+    params,
+    paramsSerializer: (params:any) => qs.stringify(params)
+  });
+}
+/**
+ * @description 市州互转-转入
+ * @param {object} params
+ */
+export const cityConversionIn = (params: object) => {
+  return request({
+    url: `/api/v1/TranspondCity/order_receive_in_list`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 市州互转-转出
+ * @param {object} params
+ */
+export const cityConversionOut = (params: object) => {
+  return request({
+    url: `/api/v1/TranspondCity/order_transfer_out_list`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 回退错件统计
+ * @param {object} params
+ */
+export const centerReturnError = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/reTransact`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 回退错件统计明细
+ * @param {object} params
+ */
+export const centerReturnErrorDetail = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/reTransact_detail`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 回退错件统计明细基础信息
+ * @param {object} params
+ */
+export const centerReturnErrorDetailBase = (params?: object) => {
+  return request({
+    url: `/api/v1/BiOrder/reTransact_base`,
+    method: 'get',
+    params,
+  });
+}

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

@@ -0,0 +1,71 @@
+/*
+ * @Author: zc
+ * @description 数据统计-部门统计
+ */
+import request from '@/utils/request';
+/**
+ * @description 部门延期统计
+ * @param {object} params
+ */
+export const departmentDelay = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/order-delay-data-list`,
+    method: 'get',
+    params,
+  });
+};
+/**
+ * @description 部门受理类型统计周期
+ * @param {object} params
+ */
+export const departmentAcceptType = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/department_acceptance_type_statistics`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 部门数据统计列表查询
+ * @param {object} params
+ */
+export const departmentList = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/org_data_list`,
+    method: 'get',
+    params,
+  });
+};
+/**
+ * @description 部门满意度统计
+ * @param {object} params
+ */
+export const departmentSatisfaction = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/visit-org-satisfaction-statistics`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 部门满意度统计明细
+ * @param {object} params
+ */
+export const departmentSatisfactionDetail = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/visit-org-satisfaction-detail`,
+    method: 'get',
+    params,
+  });
+}
+/**
+ * @description 子部门满意度统计明细
+ * @param {object} params
+ */
+export const departmentSatisfactionOrg = (params: object) => {
+  return request({
+    url: `/api/v1/BiOrder/visit-org-statisfaction-org-detail`,
+    method: 'get',
+    params,
+  });
+}

+ 8 - 41
src/api/statistics/order.ts

@@ -3,17 +3,6 @@
  * @description 数据统计-部门数据统计
  */
 import request from '@/utils/request';
-/**
- * @description 部门数据统计列表查询
- * @param {object} params
- */
-export const departmentList = (params: object) => {
-	return request({
-		url: `/api/v1/BiOrder/org_data_list`,
-		method: 'get',
-		params,
-	});
-};
 /**
  * @description 中心统计列表查询
  * @param {object} params
@@ -47,17 +36,6 @@ export const departmentUnsatisfiedDetail = (params: object) => {
 		params,
 	});
 };
-/**
- * @description 部门延期统计
- * @param {object} params
- */
-export const departmentDelay = (params: object) => {
-	return request({
-		url: `/api/v1/BiOrder/order-delay-data-list`,
-		method: 'get',
-		params,
-	});
-};
 /**
  * @description 特提统计
  * @param {object} params
@@ -112,7 +90,7 @@ export const departmentTopTen = (params: object) => {
 		method: 'get',
 		params,
 	});
-}
+};
 /**
  * @description 回访量统计
  * @param {object} params
@@ -123,7 +101,7 @@ export const departmentVisit = (params: object) => {
 		method: 'get',
 		params,
 	});
-}
+};
 /**
  * @description 热点类型小类统计
  * @param {object} params
@@ -134,18 +112,7 @@ export const departmentHotSmall = (params: object) => {
 		method: 'get',
 		params,
 	});
-}
-/**
- * @description 中心报表统计
- * @param {object} params
- */
-export const centerReport = (params: object) => {
-	return request({
-		url: `/api/v1/BiOrder/center_report_forms_statistics`,
-		method: 'get',
-		params,
-	});
-}
+};
 /**
  * @description 部门受理类型统计周期
  * @param {object} params
@@ -156,15 +123,15 @@ export const departmentAcceptType = (params: object) => {
 		method: 'get',
 		params,
 	});
-}
+};
 /**
- * @description 部门办件统计表
+ * @description 部门受理类型统计周期明细
  * @param {object} params
  */
-export const departmentHandle = (params: object) => {
+export const departmentAcceptTypeDetail = (params: object) => {
 	return request({
-		url: `/api/v1/BiOrder/department_handle_statistics`,
+		url: `/api/v1/BiOrder/department_acceptance_type_order_list`,
 		method: 'get',
 		params,
 	});
-}
+};

+ 18 - 4
src/api/system/roles.ts

@@ -76,13 +76,14 @@ export const setRolePower = (data: object) => {
 };
 /**
  * @description 根据id批量查询用户
- * @param {object} params  ids
+ * @param {object} data  ids
  * @return {*}
  */
-export const getUserListByIds = (params: any) => {
+export const getUserListByIds = (data: any) => {
 	return request({
-		url: `/api/v1/User${params}`,
-		method: 'get',
+		url: `/api/v1/User/range`,
+		method: 'post',
+		data
 	});
 };
 /**
@@ -151,3 +152,16 @@ export const baseData = () => {
 		method: 'get',
 	});
 };
+
+/**
+ * @description 基础数据
+ * @param {object} params
+ * @return {*}
+ */
+export const getRoleBaseData = (params?: object) => {
+	return request({
+		url: `/api/v1/Role/role/basedata`,
+		method: 'get',
+		params,
+	});
+};

+ 10 - 0
src/api/system/user.ts

@@ -84,6 +84,16 @@ export const getCanUseOrg = () => {
 		method: 'get',
 	});
 };
+/**
+ * @description 获取可用组织架构树形(部门用户只能查看和添加所属部门及其下级部门的用户)
+ * @return {*}
+ */
+export const getCanUseOrgByUser = () => {
+	return request({
+		url: `/api/v1/Org/getcanuseorgforuser`,
+		method: 'get',
+	});
+};
 /**
  * @description 获取用户页面基础信息
  * @return {*}

+ 159 - 0
src/api/system/workforce.ts

@@ -0,0 +1,159 @@
+/*
+ * @Author: zc
+ * @description 系统管理- 班次管理
+ */
+import request from '@/utils/request';
+/**
+ * @description 获取排班人员列表
+ * @param {object} params
+ * @return {*}
+ */
+export const getWorkforceList = (params?: object) => {
+	return request({
+		url: '/api/v1/Scheduling/user_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 新增排班人员
+ * @return {*}
+ * @param data
+ */
+export const addWorkforce = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling/user',
+		method: 'post',
+		data,
+	});
+};
+/**
+ * @description 删除排版人员
+ * @return {*}
+ * @param data
+ */
+export const deleteWorkforce = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling/user',
+		method: 'delete',
+		data,
+	});
+};
+/**
+ * @description 获取班次列表
+ * @param {object} params
+ * @return {*}
+ */
+export const getWorkforceClassList = (params?: object) => {
+	return request({
+		url: '/api/v1/Scheduling/shift_list',
+		method: 'get',
+		params,
+	});
+};
+/**
+ * @description 新增班次
+ * @return {*}
+ * @param data
+ */
+export const addWorkforceClass = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling/shift',
+		method: 'post',
+		data,
+	});
+};
+/**
+ * @description 获取班次详情
+ * @return {*}
+ * @param id
+ */
+export const getWorkforceClassDetail = (id: string) => {
+	return request({
+		url: `/api/v1/Scheduling/shift/${id}`,
+		method: 'get',
+	});
+};
+/**
+ * @description 编辑班次
+ * @return {*}
+ * @param data
+ */
+export const updateWorkforceClass = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling/shift',
+		method: 'put',
+		data,
+	});
+};
+/**
+ * @description 删除班次
+ * @return {*}
+ * @param data
+ */
+export const deleteWorkforceClass = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling/shift',
+		method: 'delete',
+		data,
+	});
+};
+/**
+ * @description 获取班次人员列表
+ * @return {*}
+ * @param params
+ */
+export const getWorkforceClassUserList = (params: object) => {
+	return request({
+		url: '/api/v1/Scheduling/data',
+		method: 'get',
+		params,
+	});
+}
+/**
+ * @description 批量排班
+ * @return {*}
+ * @param data
+ */
+export const batchScheduling = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling',
+		method: 'post',
+		data,
+	});
+}
+/**
+ * @description 排班修改
+ * @return {*}
+ * @param data
+ */
+export const updateScheduling = (data: object) => {
+	return request({
+		url: '/api/v1/Scheduling',
+		method: 'put',
+		data,
+	});
+}
+/**
+ * @description 排班详情
+ * @return {*}
+ * @param id
+ */
+export const getSchedulingDetail = (id: string) => {
+	return request({
+		url: `/api/v1/Scheduling/${id}`,
+		method: 'get',
+	});
+}
+/**
+ * @description 批量删除排班
+ * @param {object} data
+ * @return {*}
+ */
+export const deleteScheduling = (data:object) => {
+	return request({
+		url: `/api/v1/Scheduling`,
+		method: 'delete',
+		data
+	});
+}

+ 12 - 0
src/api/tels/extension.ts

@@ -15,3 +15,15 @@ export const extensionPaged = (params?: object) => {
 		params,
 	});
 };
+/**
+ * @description 强制下线
+ * @param {object} params
+ * @return {*}
+ */
+export const forceLogout = (params?: object) => {
+	return request({
+		url: `/api/v1/IPPbx/off-duty-manage`,
+		method: 'get',
+		params,
+	});
+}

二進制
src/assets/images/login/bg.png


二進制
src/assets/images/login/code1.png


二進制
src/assets/images/login/code2.png


二進制
src/assets/images/login/code3.png


二進制
src/assets/images/login/code4.png


二進制
src/assets/images/login/code5.png


+ 3 - 3
src/components/AnnexList/index.vue

@@ -54,8 +54,8 @@
 </template>
 <script setup lang="ts" name="annexList">
 import { computed, ref, watch } from 'vue';
-import { checkFile, downloadFile, fileType } from '@/utils/tools';
-import { ElButton, ElMessage, ElMessageBox } from 'element-plus';
+import { checkFile, downloadFileBySrc, fileType } from "@/utils/tools";
+import { ElMessage, ElMessageBox } from 'element-plus';
 import Other from '@/utils/other';
 const emit = defineEmits(['update:modelValue', 'update:format']);
 const props = defineProps({
@@ -178,7 +178,7 @@ const handleDownload = (uploadFile: any) => {
 		.then(() => {
 			const fileName = uploadFile.name;
 			const url = import.meta.env.VITE_FILE_PREFIX + path;
-			downloadFile(url, fileName);
+      downloadFileBySrc(url, fileName);
 		})
 		.catch(() => {});
 };

+ 5 - 4
src/components/AudioPlayer/index.vue

@@ -73,7 +73,8 @@
 import { reactive, ref, computed, watch, nextTick } from 'vue';
 import { formatDuration } from '@/utils/formatTime';
 import { ElMessage } from 'element-plus';
-import { downloadFile } from '@/utils/tools';
+import { downloadFileBySrc, downloadFileByStream } from "@/utils/tools";
+import { fileDownload } from "@/api/public/file";
 
 // 定义父组件传过来的值
 const props = defineProps({
@@ -239,9 +240,9 @@ const mute = () => {
 };
 // 下载
 const downLoad = () => {
-	downloadFile(props.url, props.fileName);
-	// window.open(props.url);
-	// downloadFile({ name: props?.fileName ?? '', url: props.url })
+  fileDownload({path:props.url}).then((res:any) => {
+    downloadFileByStream(res, <string>props.fileName);
+  });
 };
 nextTick(() => {
 	audioRef.value.onerror = () => {

+ 3 - 9
src/components/Hotspot/index.vue

@@ -116,7 +116,7 @@ const treeProps = computed(() => {
 	};
 });
 
-const emit = defineEmits(['choose', 'update:modelValue']);
+const emit = defineEmits(['choose', 'update:modelValue', 'confirm']);
 
 const state = reactive<any>({
 	id: '',
@@ -249,8 +249,7 @@ const checkChange = (val: any, e: any) => {
 };
 // 多选确定
 const chooseHotSpot = () => {
-	emit('update:modelValue', state.checkedKeys);
-	emit('choose', state.checkedKeys, state.checkedNodes, state.externalArr);
+	emit('confirm', state.checkedKeys, state.checkedNodes, state.externalArr);
 	selectRef.value.blur();
 };
 // 重置
@@ -258,8 +257,7 @@ const reset = () => {
 	state.checkedKeys = [];
 	treeRef.value.setCheckedKeys([]);
 	state.name = '';
-	emit('choose', [], [], []);
-	emit('update:modelValue', []);
+  emit('update:modelValue', []);
 };
 // 递归查找父级Id
 const getParentId = (val: any, arr: string[]) => {
@@ -276,14 +274,10 @@ const clear = () => {
 		treeRef.value.setCheckedKeys([]);
 		state.name = '';
 		state.id = [];
-		emit('choose', [], [], []);
-		emit('update:modelValue', []);
 	} else {
 		state.id = '';
 		state.name = '';
 		treeRef.value.setCurrentKey(null);
-		emit('choose', {});
-		emit('update:modelValue', '');
 	}
 };
 defineExpose({

+ 1 - 1
src/components/IconSelector/index.vue

@@ -195,7 +195,7 @@ const onClearFontIcon = () => {
 // 获取 input 的宽度
 const getInputWidth = () => {
   nextTick(() => {
-    state.fontIconWidth = inputWidthRef?.value.$el.offsetWidth;
+    state.fontIconWidth = inputWidthRef.value?.$el?.offsetWidth;
   });
 };
 // 监听页面宽度改变

+ 0 - 82
src/components/ImgVerify/hooks.ts

@@ -1,82 +0,0 @@
-import { onMounted, ref } from 'vue';
-
-/**
- * 绘制图形验证码
- * @param width - 图形宽度
- * @param height - 图形高度
- */
-export const useImageVerify = (width = 120, height = 32) => {
-	const domRef = ref<HTMLCanvasElement>();
-	const imgCode = ref('');
-
-	function setImgCode(code: string) {
-		imgCode.value = code;
-	}
-
-	function getImgCode() {
-		if (!domRef.value) return;
-		imgCode.value = draw(domRef.value, width, height);
-	}
-
-	onMounted(() => {
-		getImgCode();
-	});
-
-	return {
-		domRef,
-		imgCode,
-		setImgCode,
-		getImgCode,
-	};
-};
-
-function randomNum(min: number, max: number) {
-	return Math.floor(Math.random() * (max - min) + min);
-}
-
-function randomColor(min: number, max: number) {
-	const r = randomNum(min, max);
-	const g = randomNum(min, max);
-	const b = randomNum(min, max);
-	return `rgb(${r},${g},${b})`;
-}
-
-function draw(dom: HTMLCanvasElement, width: number, height: number) {
-	let imgCode = '';
-	const Random_STRING: string = '23456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
-	const ctx = dom.getContext('2d');
-	if (!ctx) return imgCode;
-
-	ctx.fillStyle = randomColor(255, 255);
-	ctx.fillRect(2, 0, width, height);
-	for (let i = 0; i < 4; i += 1) {
-		const text = Random_STRING[randomNum(0, Random_STRING.length)];
-		imgCode += text;
-		const fontSize = randomNum(18, 41);
-		const deg = randomNum(-30, 30);
-		ctx.font = `${fontSize}px Simhei`;
-		ctx.textBaseline = 'top';
-		ctx.fillStyle = randomColor(80, 150);
-		ctx.save();
-		ctx.translate(30 * i + 15, 15);
-		ctx.rotate((deg * Math.PI) / 180);
-		ctx.fillText(text, -15 + 5, -15);
-		ctx.restore();
-	}
-	for (let i = 0; i < 5; i += 1) {
-		ctx.beginPath();
-		ctx.moveTo(randomNum(0, width), randomNum(0, height));
-		ctx.lineTo(randomNum(0, width), randomNum(0, height));
-		ctx.strokeStyle = randomColor(180, 230);
-		ctx.closePath();
-		ctx.stroke();
-	}
-	for (let i = 0; i < 41; i += 1) {
-		ctx.beginPath();
-		ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
-		ctx.closePath();
-		ctx.fillStyle = randomColor(150, 200);
-		ctx.fill();
-	}
-	return imgCode;
-}

+ 0 - 35
src/components/ImgVerify/index.vue

@@ -1,35 +0,0 @@
-<template>
-	<canvas ref="domRef" width="120" height="32" class="cursor-pointer" @click="getImgCode" />
-</template>
-<script setup lang="ts" name="ReImageVerify">
-import { watch } from 'vue';
-import { useImageVerify } from './hooks';
-interface Props {
-	code?: string;
-}
-
-interface Emits {
-	(e: 'update:code', code: string): void;
-}
-
-const props = withDefaults(defineProps<Props>(), {
-	code: '',
-});
-
-const emit = defineEmits<Emits>();
-
-const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
-
-watch(
-	() => props.code,
-	(newValue) => {
-		setImgCode(newValue);
-	}
-);
-watch(imgCode, (newValue) => {
-	// 不区分大小写
-	emit('update:code', newValue.toUpperCase());
-});
-
-defineExpose({ getImgCode });
-</script>

+ 0 - 4
src/components/LogicFlow/index.vue

@@ -343,10 +343,6 @@ const closePage = () => {
 	// 关闭当前 tagsView
 	mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
 	mittBus.emit('clearCache', 'systemWorkflow');
-	if (!router.hasRoute('systemWorkflow')) {
-		ElMessage.warning('未找到流程模板列表页面');
-		return;
-	}
 	router.push({
 		name: 'systemWorkflow',
 		state: {

+ 51 - 68
src/components/OrderDetail/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-button link type="primary" @click="onOrderDetail" title="点击查看工单详情">
+	<el-button link :type="props.type" @click.stop="onOrderDetail" title="点击查看工单详情">
 		<slot>工单详情</slot>
 	</el-button>
 	<el-dialog
@@ -40,8 +40,8 @@
 										size="small"
 										type="primary"
 										class="ml8"
-										@click="recordFile(state.ruleForm.recordingFileUrl)"
-										v-if="state.ruleForm.recordingFileUrl"
+										@click="recordFile(state.ruleForm?.recordingAbsolutePath)"
+										v-if="state.ruleForm?.recordingAbsolutePath"
 										>录音文件</el-button
 									>
 								</el-form-item>
@@ -85,7 +85,7 @@
 								<el-form-item label="专班名称"> {{ state.ruleForm.zhuanBanMingCheng }} </el-form-item>
 							</el-col>
 							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-								<el-form-item label="事发地址"> {{ state.ruleForm.address }} </el-form-item>
+								<el-form-item label="事发地址"> {{ state.ruleForm.fullAddress }} </el-form-item>
 							</el-col>
 						</el-row>
 					</el-form>
@@ -166,7 +166,7 @@
 							</el-col>
 							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 								<el-form-item label="受理内容">
-									{{ state.ruleForm.content }}
+									<div v-html="showKeyWord(state.ruleForm.content, state.ruleForm.sensitive)"></div>
 								</el-form-item>
 							</el-col>
 							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
@@ -205,9 +205,18 @@
 							<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
 								<el-form-item label="工单期满时间">
 									{{ formatDate(state.ruleForm.expiredTime, 'YYYY-mm-dd HH:MM:SS') }}
-									<span v-if="state.ruleForm?.delayString" class="color-danger">(已延期{{ state.ruleForm?.delayString }})</span></el-form-item
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" v-if="state.ruleForm?.delayString">
+								<el-form-item label="信件延期">
+									<span class="color-danger">{{ state.ruleForm?.delayString }}</span></el-form-item
 								>
 							</el-col>
+							<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6">
+								<el-form-item label="省期满时间">
+									{{ formatDate(state.ruleForm.expiredTimeProvince, '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.ruleForm.actualOpinion }} </el-form-item>
 							</el-col>
@@ -280,43 +289,6 @@
 		</div>
 		<!-- 回访记录 -->
 		<div v-show="state.activeName === '2'">
-			<!--			<el-table :data="state.ruleForm.orderVisits">
-				<el-table-column prop="visitStateText" label="回访状态" show-overflow-tooltip width="100"></el-table-column>
-				<el-table-column prop="visitTypeText" label="回访方式" show-overflow-tooltip width="100"></el-table-column>
-				<el-table-column prop="creationTime" label="回访任务创建时间" show-overflow-tooltip width="170">
-					<template #default="{ row }">
-						<span>{{ formatDate(row.creationTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
-					</template>
-				</el-table-column>
-				<el-table-column prop="employeeName" label="回访人" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="visitTime" label="回访时间" show-overflow-tooltip width="170">
-					<template #default="{ row }">
-						<span>{{ formatDate(row.visitTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
-					</template>
-				</el-table-column>
-				<el-table-column label="语音评价" show-overflow-tooltip>
-					<template #default="{ row }">
-						<span v-for="item in row.orderVisitDetails">
-							<span v-if="item.visitTarget === 10">{{ item.voiceEvaluateText }}</span>
-						</span>
-					</template>
-				</el-table-column>
-				<el-table-column label="话务员满意度" show-overflow-tooltip>
-					<template #default="{ row }">
-						<span v-for="item in row.orderVisitDetails">
-							<span v-if="item.visitTarget === 10">{{ item.seatEvaluateText }}</span>
-						</span>
-					</template>
-				</el-table-column>
-				<el-table-column prop="statusText" label="操作" width="100" fixed="right" align="center">
-					<template #default="{ row }">
-						<el-button @click="onVisitDetail(row)" link type="primary"> 查看 </el-button>
-					</template>
-				</el-table-column>
-				<template #empty>
-					<Empty />
-				</template>
-			</el-table>-->
 			<el-form
 				label-width="120px"
 				label-position="left"
@@ -398,11 +370,11 @@
 								{{ item.orgNoSatisfiedReason?.map((item) => item.value).join(',') }}
 							</el-form-item>
 						</el-col>
-						<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+<!--						<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
 							<el-form-item label="部门办件态度">
 								{{ item.orgHandledAttitude?.value }}
 							</el-form-item>
-						</el-col>
+						</el-col>-->
 						<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 							<el-form-item label="部门回访内容">
 								{{ item.visitContent }}
@@ -435,7 +407,7 @@
 				<el-button type="primary" @click="onRecord" :loading="state.loading" v-if="state.ruleForm?.workflowId">流程明细</el-button>
 				<!-- 有流程信息就可以撤回 -->
 				<el-button type="primary" @click="onSpecialHandle" :loading="state.loading" v-if="state.ruleForm?.workflowId" v-auth="'business:order:teti'"
-					>撤 回(特提</el-button
+					>特 提</el-button
 				>
 				<!-- 办理中和会签中,可以督办 -->
 				<el-button
@@ -473,15 +445,15 @@
 					@click="onCancelDelay"
 					:loading="state.loading"
 					v-auth="'business:order:cancelDelay'"
-					v-if="!state.ruleForm?.isCanDelay"
+					v-if="state.ruleForm?.isCanCancelDelay"
 					>取消延期</el-button
 				>
-				<!-- 办理中和会签中并且应该自己办理并且没有在延期中 -->
+				<!-- 办理中和会签中并且应该自己办理 -->
 				<el-button
 					type="primary"
 					@click="onSubmit('工单办理')"
 					:loading="state.loading"
-					v-if="[100, 200].includes(state.ruleForm?.status) && state.ruleForm?.canHandle && state.ruleForm?.isCanDelay"
+					v-if="[100, 200].includes(state.ruleForm?.status) && state.ruleForm?.canHandle"
 					v-auth="'business:order:handle'"
 					>办 理</el-button
 				>
@@ -490,14 +462,6 @@
           >补 充</el-button
         >-->
 				<!--				<el-button type="primary" @click="onRevoke" :loading="state.loading">撤 销</el-button>-->
-				<!-- 工单未归档都可以撤回 -->
-				<!--				<el-button
-          type="primary"
-          @click="onSubmit('工单撤回')"
-          :loading="state.loading"
-          v-if="[0].includes(state.ruleForm.workflow?.status)"
-          >撤 回(特提)</el-button
-        >-->
 				<!-- 办理中和会签中并且应该自己办理 -->
 				<el-button
 					type="primary"
@@ -513,7 +477,7 @@
 	<!-- 扩展信息 -->
 	<order-expand-detail ref="orderExpandDetailRef" />
 	<!-- 流转记录 -->
-	<audit-record ref="AuditRecordRef">
+	<audit-record ref="auditRecordRef">
 		<template #header>
 			<el-form label-width="90px" ref="ruleFormRef">
 				<el-row :gutter="35">
@@ -550,19 +514,18 @@ import { defineAsyncComponent, PropType, reactive, ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { throttle, transformFile } from '@/utils/tools';
 import { cancelDelay, endCounterSign, orderDetail } from '@/api/business/order';
-import { ElButton, ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessage, ElMessageBox } from 'element-plus';
 import { ola } from '@/utils/ola_api';
 import { formatDate } from '@/utils/formatTime';
-import Empty from '@/components/Empty/index.vue';
 
 // 引入组件
 const OrderExpandDetail = defineAsyncComponent(() => import('@/views/business/order/components/Order-expand-detail.vue')); // 扩展信息
 const OrderSupply = defineAsyncComponent(() => import('@/views/business/order/components/Order-supply.vue')); // 工单补充
 const OrderRevoke = defineAsyncComponent(() => import('@/views/business/order/components/Order-revoke.vue')); // 工单撤销
 const OrderSupervise = defineAsyncComponent(() => import('@/views/business/supervise/components/Order-supervise.vue')); // 工单督办
-const OrderUrge = defineAsyncComponent(() => import('@/views/query/urge/components/Order-urge.vue')); // 工单催办
+const OrderUrge = defineAsyncComponent(() => import('@/views/business/urge/components/Order-urge.vue')); // 工单催办
 const OrderRepeat = defineAsyncComponent(() => import('@/views/business/order/components/Order-repeat.vue')); // 重复工单
-const AuditRecord = defineAsyncComponent(() => import('@/components/AuditRecord/index.vue')); // 审核记录
+const AuditRecord = defineAsyncComponent(() => import('@/components/AuditRecord/index.vue')); // 流程明细
 const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue')); // 附件列表
 const ProcessAudit = defineAsyncComponent(() => import('@/components/ProcessAudit/index.vue')); // 流程审批
 const HistoryOrder = defineAsyncComponent(() => import('@/views/todo/seats/accept/History.vue')); // 历史工单
@@ -570,13 +533,18 @@ const CitizenPortrait = defineAsyncComponent(() => import('@/views/todo/seats/ac
 const SpecialHandleOrder = defineAsyncComponent(() => import('@/views/business/special/components/Special-apply-order.vue')); // 特提申请
 const PlayRecord = defineAsyncComponent(() => import('@/views/tels/callLog/component/Play-record.vue')); // 播放录音
 
+type ButtonType = '' | 'default' | 'success' | 'warning' | 'info' | 'text' | 'primary' | 'danger';
 const props = defineProps({
 	order: {
 		// 工单详情
-		type: Object as PropType<any>,
+		type: [Object, null] as PropType<string | null>,
 		default: {},
 		required: true,
 	},
+	type: {
+		type: String as PropType<ButtonType>,
+		default: 'primary',
+	},
 	source: {
 		// 来源(根据来源判断 有特殊逻辑处理)
 		type: String,
@@ -621,6 +589,21 @@ const state = reactive<any>({
 const showMaskNumber = ref<boolean>(true); // 是否展示号码
 const ruleFormRef = ref<RefType>(); // 表单ref
 const router = useRouter(); // 路由
+// 高亮关键词
+const showKeyWord = (val: string[], keywordArr: string[]) => {
+	let str = val;
+	if (keywordArr && keywordArr.length) {
+		// 2.定制关键词对应正则
+		keywordArr.forEach((e) => {
+			let regStr = '' + `(${e})`;
+			let reg = new RegExp(regStr, 'g');
+			// 3.正则替换,关键词飘红
+			str = str.replace(reg, '<span class="color-danger font-bold"">' + e + '</span>');
+		});
+	}
+	return str;
+};
+
 // 查看工单详情
 const getOrderDetail = async (id: string) => {
 	state.loading = true;
@@ -652,7 +635,7 @@ const getHistoryList = async () => {
 };
 // 查询市民画像
 const citizenPortraitRef = ref<RefType>(); // 市民画像
-const getPortraitList = async (id: string) => {
+const getPortraitList = async () => {
 	state.loading = true;
 	try {
 		citizenPortraitRef.value?.getCitizen(state.ruleForm);
@@ -708,7 +691,7 @@ const closeDialog = () => {
 // 查看录音文件
 const playRecordRef = ref<RefType>();
 const recordFile = (url: string) => {
-	playRecordRef.value.openDialog(url);
+	playRecordRef.value.openDialog(import.meta.env.VITE_RECORD_PREFIX + url);
 };
 // 电话外呼
 const callPhone = (number: number | string) => {
@@ -725,13 +708,13 @@ const showExpandInfo = () => {
 	orderExpandDetailRef.value.openDialog(state.ruleForm.orderExtension);
 };
 // 流转记录
-const AuditRecordRef = ref<RefType>(); // 流转记录
+const auditRecordRef = ref<RefType>(); // 流转记录
 const onRecord = () => {
 	const params = {
 		dialogTitle: '流转记录',
 		...state.ruleForm,
 	};
-	AuditRecordRef.value.openDialog(params);
+	auditRecordRef.value.openDialog(params);
 };
 // 提交流程
 const processAuditRef = ref<RefType>(); // 处理流程
@@ -798,7 +781,7 @@ const onVisitDetail = (row: any) => {
 // 特提
 const specialHandleOrderRef = ref<RefType>(); // 特提
 const onSpecialHandle = () => {
-	if (state.ruleForm.counterSignType || state.ruleForm.counterSignType === 0) {
+	if (state.ruleForm.status == 200) {
 		// 会签工单无法进行特提
 		ElMessage.warning('工单会签中,请先结束会签!');
 		return;

+ 5 - 4
src/components/ProTable/components/Pagination.vue

@@ -7,6 +7,7 @@
 			:layout="layout"
 			:total="total"
 			:small="small"
+			:page-sizes="pageSizes"
 			@size-change="handleSizeChange"
 			@current-change="handleCurrentChange"
 		/>
@@ -32,7 +33,7 @@ const props = defineProps({
 	pageSizes: {
 		type: Array,
 		default() {
-			return [10, 20, 30, 50, 100];
+			return [10, 20, 50, 100, 200];
 		},
 	},
 	// 移动端页码按钮的数量端默认值5
@@ -55,7 +56,7 @@ const props = defineProps({
 });
 
 // 定义子组件向父组件传值/事件
-const emit = defineEmits(['update:current-page', 'update:page-size', 'paginationEvent']);
+const emit = defineEmits(['update:current-page', 'update:page-size', 'pagination']);
 
 // 定义变量内容
 const small = ref(false);
@@ -87,11 +88,11 @@ const handleSizeChange = (val: any) => {
 	if (currentPage.value > pageMax) {
 		currentPage.value = pageMax;
 	}
-	emit('paginationEvent', { page: currentPage.value, limit: val });
+	emit('pagination', { page: currentPage.value, limit: val });
 };
 const handleCurrentChange = (val: any) => {
 	currentPage.value = val;
-	emit('paginationEvent', { page: val, limit: pageSize.value });
+	emit('pagination', { page: val, limit: pageSize.value });
 };
 onMounted(() => {
 	// 监听布局大小 改变分页的大小

+ 27 - 26
src/components/ProTable/index.vue

@@ -25,6 +25,7 @@
 			</div>
 		</div>
 		<!-- 表格主体 -->
+<!--    :scrollbar-always-on="true" 滚动条一直展示-->
 		<el-table
 			ref="tableRef"
 			v-bind="$attrs"
@@ -33,6 +34,7 @@
 			:row-key="rowKey"
 			@selection-change="selectionChange"
 			v-loading="loading"
+      :scrollbar-always-on="true"
 		>
 			<!-- 默认插槽 -->
 			<slot />
@@ -54,10 +56,6 @@
 						<el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
 							<i></i>
 						</el-radio>
-						<!-- sort -->
-						<el-tag v-if="item.type == 'sort'" class="move">
-							<SvgIcon name="ele-Rank" />
-						</el-tag>
 					</template>
 				</el-table-column>
 				<!-- other -->
@@ -78,7 +76,7 @@
 		</el-table>
 		<!-- 分页组件 -->
 		<slot name="pagination">
-			<PaginationEl v-if="pagination" @paginationEvent="onRefresh" :total="total" v-model:current-page="pageIndex" v-model:page-size="pageSize" />
+			<PaginationEl v-if="pagination" @pagination="onRefresh" :total="total" v-model:current-page="pageIndex" v-model:page-size="pageSize" />
 		</slot>
 	</div>
 	<!-- 列设置 -->
@@ -86,14 +84,13 @@
 </template>
 
 <script setup lang="ts" name="ProTable">
-import { ref, provide, onMounted, unref, computed, reactive, PropType } from 'vue';
+import { ref, provide, onMounted, unref, computed, reactive, PropType, watch } from 'vue';
 import { ElTable } from 'element-plus';
 import { useSelection } from '@/hooks/useSelection';
 import { ColumnProps, TypeProps } from '@/components/ProTable/interface';
 import PaginationEl from './components/Pagination.vue';
 import ColSetting from './components/ColSetting.vue';
 import TableColumn from './components/TableColumn.vue';
-import Sortable from 'sortablejs';
 import { handleProp } from '@/utils/tools';
 
 // 接受父组件参数,配置默认值
@@ -134,7 +131,8 @@ const props = defineProps({
 	toolButton: {
 		// 是否显示表格功能按钮 ==> 非必传(默认为true)
 		type: [Array, Boolean],
-		default: true,
+		// default: true,
+		default: ['refresh', 'setting'],
 	},
 	rowKey: {
 		// 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
@@ -146,6 +144,10 @@ const props = defineProps({
 		type: Boolean,
 		default: false,
 	},
+	radio: {
+		type: String,
+		default: '',
+	},
 });
 const emit = defineEmits([
 	'dargSort',
@@ -157,6 +159,7 @@ const emit = defineEmits([
 	'exportAll',
 	'update:pageSize',
 	'update:pageIndex',
+	'update:radio',
 ]);
 const pageSize = computed({
 	get() {
@@ -174,6 +177,14 @@ const pageIndex = computed({
 		emit('update:pageIndex', val);
 	},
 });
+const radio = computed({
+	get() {
+		return props.radio;
+	},
+	set(val) {
+		emit('update:radio', val);
+	},
+});
 // table 实例
 const tableRef = ref<InstanceType<typeof ElTable>>();
 
@@ -185,9 +196,6 @@ const showToolButton = (key: 'refresh' | 'setting' | 'exportCurrent' | 'exportAl
 	return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
 };
 
-// 单选值
-const radio = ref('');
-
 // 表格多选 Hooks
 const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey);
 
@@ -196,7 +204,6 @@ const clearSelection = () => tableRef.value!.clearSelection();
 
 // 初始化表格数据 && 拖拽排序
 onMounted(() => {
-	dragSort();
 	getSelectColumns();
 });
 
@@ -267,22 +274,9 @@ const colSetting = tableColumns!.filter((item) => {
 });
 const openColSetting = () => colRef.value.openColSetting();
 
-// 拖拽排序
-const dragSort = () => {
-	const tbody = document.querySelector('.el-table__body-wrapper tbody') as HTMLElement;
-	Sortable.create(tbody, {
-		handle: '.move',
-		animation: 300,
-		onEnd({ newIndex, oldIndex }) {
-			const [removedItem] = processTableData.value.splice(oldIndex!, 1);
-			processTableData.value.splice(newIndex!, 0, removedItem);
-			emit('dargSort', { newIndex, oldIndex });
-		},
-	});
-};
 // 刷新事件
 const onRefresh = () => {
-  clearSelection();
+	clearSelection();
 	emit('updateTable');
 };
 const filterColumns = ref([]);
@@ -306,6 +300,13 @@ const exportCurrent = () => {
 const exportAll = () => {
 	emit('exportAll');
 };
+watch(
+	// 监听table数据改变后重置选择
+	() => props.data,
+	() => {
+		clearSelection();
+	}
+);
 // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
 defineExpose({
 	element: tableRef,

+ 110 - 129
src/components/ProcessAudit/index.vue

@@ -135,48 +135,6 @@
 					</el-col>
 				</el-row>
 			</el-form>
-			<el-form :model="state.redoForm" label-width="110px" ref="redoFormRef" v-if="state.processType === '工单重办'">
-				<el-row :gutter="10">
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="工单编码"> {{ state.orderDetail.no }} </el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="工单标题"> {{ state.orderDetail.title }} </el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="申请人"> {{ userInfos.name }} </el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="申请部门"> {{ userInfos.orgName }} </el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="申请时间"> {{ dayjs(Date()).format('YYYY-MM-DD HH:mm:ss') }} </el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
-						<el-form-item label="重办理由" prop="orderRedoReason" :rules="[{ required: true, message: '请选择重办理由', trigger: 'change' }]">
-							<el-select v-model="state.redoForm.orderRedoReason" placeholder="请选择重办理由" clearable class="w100">
-								<el-option v-for="item in orderRedoReasonOptions" :value="item.key" :key="item.key" :label="item.value" />
-							</el-select>
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-						<el-form-item label="重办意见" prop="redoOpinion" :rules="[{ required: true, message: '请填写重办意见', trigger: 'blur' }]">
-							<common-advice
-								@chooseAdvice="chooseAdviceRedo"
-								v-model="state.redoForm.redoOpinion"
-								placeholder="请填写重办意见"
-								:loading="state.loading"
-								:commonEnum="commonEnum.OrderCirculation"
-							/>
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-						<el-form-item label="附件">
-							<annex-list name="重办附件" v-model:format="handleFilesDiscern" :businessId="state.orderDetail.id" classify="重办上传" />
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
 		</div>
 
 		<el-form :model="state.ruleForm" label-width="110px" ref="ruleFormRef" v-show="activeStep === 1" v-loading="state.loading">
@@ -205,7 +163,12 @@
 							</el-form-item>
 						</el-col>
 						<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="!returnArr.includes(state.processType) && showHandlers">
-							<el-form-item label="办理对象" prop="nextHandlers" :rules="[{ required: nextHandlersRequired, message: '请选择办理对象', trigger: 'change' }]">
+							<el-form-item
+								label="办理对象"
+								prop="nextHandlers"
+								:rules="[{ required: nextHandlersRequired, message: '请选择办理对象', trigger: 'change' }]"
+								v-if="!returnArr.includes(state.processType) && showHandlers"
+							>
 								<el-select-v2
 									v-model="state.ruleForm.nextHandlers"
 									:options="state.handlerOptions"
@@ -283,7 +246,11 @@
 					</el-col>
 					<!-- 非退回流程都需要选择并且如果选择了结束节点就不需要选择办理对象 -->
 					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="!returnArr.includes(state.processType) && showHandlers">
-						<el-form-item label="办理对象" prop="nextHandlers" :rules="[{ required: nextHandlersRequired, message: '请选择办理对象', trigger: 'change' }]">
+						<el-form-item
+							label="办理对象"
+							prop="nextHandlers"
+							:rules="[{ required: nextHandlersRequired, message: '请选择办理对象', trigger: 'change' }]"
+						>
 							<el-select-v2
 								v-model="state.ruleForm.nextHandlers"
 								:options="state.handlerOptions"
@@ -321,6 +288,7 @@
 											@input="computeTimeNext"
 											:min="1"
 											:max="99"
+											disabled
 										></el-input-number>
 									</el-col>
 									<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-loading="state.loading">
@@ -338,11 +306,11 @@
 								</el-row>
 							</el-form-item>
 						</el-col>
-						<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+						<!--						<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
 							<el-form-item label="工单期满时间">
 								{{ state.ruleForm.endTime }}
 							</el-form-item>
-						</el-col>
+						</el-col>-->
 						<!--					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" v-if="handelArr.includes(state.processType)">
               <el-form-item label="节点期满时间" prop="expiredTime" :rules="[{ required: true, message: '请选择节点期满时间', trigger: 'change' }]">
                 <el-date-picker
@@ -446,7 +414,7 @@ import other from '@/utils/other';
 import { useUserInfo } from '@/stores/userInfo';
 import { storeToRefs } from 'pinia';
 import { commonEnum } from '@/utils/constants';
-import { orderFlowParams, orderHandle, orderStartFlow, orderTimeConfig } from "@/api/business/order";
+import { orderFlowParams, orderHandle, orderStartFlow, orderTimeConfig } from '@/api/business/order';
 import {
 	orderPrevious,
 	workflowNext,
@@ -455,12 +423,10 @@ import {
 	workflowPrevious,
 	workflowRecall,
 	workflowRecallParams,
-	workflowRedoParams,
 	workflowReject,
 } from '@/api/system/workflow';
-import { redoApply, redoBaseData } from '@/api/business/redo';
-import { delayApply, delayBaseData, delayCalcEndTime, workflowDelayParams } from '@/api/business/delay';
-import { discernApply, screenBaseData, workflowDiscernParams } from '@/api/business/discern';
+import { delayApply, delayApproveParams, delayBaseData, delayCalcEndTime, workflowDelayParams } from '@/api/business/delay';
+import { discernApply, discernApproveParams, screenBaseData, workflowDiscernParams } from '@/api/business/discern';
 import { debounce } from '@/utils/tools';
 import {
 	KnowledgeAdd,
@@ -472,6 +438,7 @@ import {
 } from '@/api/knowledge';
 import dayjs from 'dayjs';
 import { formatDate } from '@/utils/formatTime';
+import { useAppConfig } from '@/stores/appConfig';
 
 // 引入组件
 const CommonAdvice = defineAsyncComponent(() => import('@/components/CommonAdvice/index.vue')); // 常用意见
@@ -524,13 +491,14 @@ const state = reactive<any>({
 const ruleFormRef = ref<RefType>(); //表单组件
 const storesUserInfo = useUserInfo();
 const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
-const showStepsArr = ['延期申请', '甄别申请', '工单重办']; // 显示步骤条的流程
+const showStepsArr = ['延期申请', '甄别申请']; // 显示步骤条的流程
 const handelArr = ['工单办理']; // 处于办理状态的流程 (如果是汇总节点 需要填写办理对象等  办理流程才有期满时间)
 const returnArr = ['工单退回', '甄别退回', '延期退回']; // 退回流程 (退回流程不需要展示其他 只需要填写意见和附件即可)
-const auditArr = ['甄别审批', '延期审批']; // 审批流程
+const auditArr = ['甄别审批', '延期审批', '知识审批']; // 审批流程 (审批流程需要选择是否通过和下一环节)
+const appConfigStore = useAppConfig();
+const { AppConfigInfo } = storeToRefs(appConfigStore); // 系统配置信息
 
 const timeType = ref<EmptyArrayType>([]); // 延期申请单位
-const orderRedoReasonOptions = ref<EmptyArrayType>([]); // 重办理由
 const screenTypeOptions = ref<EmptyArrayType>([]); // 甄别类型
 
 // 打开弹窗
@@ -562,20 +530,23 @@ const openDialog = async (val: any) => {
 				timeType.value = responseDelay.result?.timeType ?? []; // 延期时间单位
 				handleResult(workflowDelayResponse);
 				break;
+			case '延期审批': // 延期审批
+				const [nextResponseDelayAudit] = await Promise.all([delayApproveParams(state.workflowId)]); //获取延期审批流程参数
+				handleResult(nextResponseDelayAudit);
+				break;
 			case '甄别申请': // 甄别申请
 				const [workflowDiscernResponse, responseDiscern] = await Promise.all([workflowDiscernParams(), screenBaseData()]); //获取开启流程参数
 				screenTypeOptions.value = responseDiscern.result?.screenType ?? []; // 甄别理由
 				handleResult(workflowDiscernResponse);
 				break;
-			case '工单重办': // 工单重办
-				const [workflowRedoResponse, responseRedo] = await Promise.all([workflowRedoParams(state.workflowId), redoBaseData()]); //获取开启流程参数
-				orderRedoReasonOptions.value = responseRedo.result?.orderRedoReasonOptions ?? []; // 重办理由
-				handleResult(workflowRedoResponse);
-				break;
 			case '工单退回': // 退回流程
 				break;
 			case '甄别退回': // 退回流程
 				break;
+			case '甄别审批': // 甄别审批
+				const [nextResponseDiscernAudit] = await Promise.all([discernApproveParams(state.workflowId)]); //获取甄别审批流程参数
+				handleResult(nextResponseDiscernAudit);
+				break;
 			case '延期退回': // 退回流程
 				break;
 			case '工单办理': // 工单办理
@@ -637,6 +608,11 @@ const handleResult = (res: any) => {
 		state.ruleForm.nextStepCode = '';
 		state.ruleForm.nextStepName = '';
 	}
+	if (res.result.opinion) {
+		setTimeout(() => {
+			state.ruleForm.opinion = res.result.opinion;
+		}, 100);
+	}
 	state.loading = false;
 };
 // 上一部
@@ -661,12 +637,6 @@ const onNext = () => {
 				activeStep.value = 1;
 			});
 			break;
-		case '工单重办':
-			redoFormRef.value?.validate((valid: boolean) => {
-				if (!valid) return;
-				activeStep.value = 1;
-			});
-			break;
 		default: // 默认下一流程
 			activeStep.value = 1;
 			break;
@@ -687,7 +657,9 @@ const selectNextStep = (val: any) => {
 	const next = state.nextStepOptions.find((item: any) => item.key === val);
 	const items = next.items; //获取下一节点
 	state.ruleForm.nextStepName = next.value; // 下一节点name
-  state.ruleForm.handlerType = next.handlerType;
+	state.ruleForm.handlerType = next.handlerType;
+	state.ruleForm.businessType = next.businessType;
+  state.ruleForm.flowDirection = next.flowDirection;
 	state.ruleForm.backToCountersignEnd = next.backToCountersignEnd ?? false; // 是否回到会签结束节点
 	state.handlerOptions = items ?? [];
 	state.handlerOptions = state.handlerOptions.map((item: any) => {
@@ -701,9 +673,11 @@ const selectNextStep = (val: any) => {
 	fastStepName.value = next.recommendOrgName; // 推荐派单处理对象
 	fastStepCode.value = next.recommendOrgId; // 推荐派单处理对象code
 
-  if(items.length === 1){ // 如果办理对象只有一个默认选中
-    state.ruleForm.nextHandlers = [items[0]];
-  }
+	if (items.length === 1) {
+		// 如果办理对象只有一个默认选中
+		state.ruleForm.nextHandlers = [items[0]];
+	}
+
 };
 //  会签是否可用 (多个办理对象,并且配置可以会签)
 const countersignAble = computed(() => {
@@ -732,11 +706,12 @@ const changeStartCountersign = (val: boolean) => {
 	}
 };
 
-// 是否展示办理对象 (只有结束节点不展示 next.stepType===2 表示为结束节点)
+// 是否展示办理对象 (结束节点不展示: next.stepType===2 表示为结束节点,下一环节为派单组时 next.businessType === 1,办理对象下拉框隐藏:AppConfigInfo.value.isAverageSendOrder= true 表示开启了平均派单 )
 const showHandlers = computed(() => {
 	const next = state.nextStepOptions.find((item: any) => item.key === state.ruleForm.nextStepCode);
+	const isAverageSendOrder = AppConfigInfo.value.isAverageSendOrder && next?.businessType === 1; // 开启平均派单并且下一个环节是派单组
 	if (!next) return true;
-	return next.stepType !== 2;
+	return next?.stepType !== 2 && !isAverageSendOrder;
 });
 // 是否是汇总节点(汇总需要填入其他参数)并且是工单办理
 const inputRealHandler = computed(() => {
@@ -804,6 +779,17 @@ const selectHandlers = () => {
 	if (state.ruleForm.nextHandlers.length > 1) {
 		// 多个办理对象 主办
 		state.ruleForm.nextMainHandler = state.ruleForm.nextHandlers[0].key;
+		// 001170:省12345平台   001177:省12345交办  这两个对应的办理对象不能参与会签
+		const cantSelect = ['001170', '001177'];
+		const isProvince12345 = state.ruleForm.nextHandlers.find((item: any) => cantSelect.includes(item.key));
+		if (isProvince12345) {
+			// 如果选择了省12345平台或者省12345交办就提示不能参与会签 并且从选择中移除
+			ElMessage({
+				message: '省12345平台和省12345交办不能参与会签',
+				grouping: true,
+			});
+			state.ruleForm.nextHandlers = state.ruleForm.nextHandlers.filter((item: any) => item.key !== '001170' && item.key !== '001177');
+		}
 	}
 	if (state.ruleForm.nextHandlers.length <= 1) {
 		// 如果只有一个办理对象就不需要发起会签
@@ -812,11 +798,14 @@ const selectHandlers = () => {
 };
 // 办理对象是否必填
 const nextHandlersRequired = ref<Boolean>(false);
-watch(() => state.ruleForm.nextStepCode,(val)=>{
-  const next = state.nextStepOptions.find((item: any) => item.key === val);
-  if (!next) return true;
-  nextHandlersRequired.value =  ![0].includes(next.handlerType);
-})
+watch(
+	() => state.ruleForm.nextStepCode,
+	(val) => {
+		const next = state.nextStepOptions.find((item: any) => item.key === val);
+		if (!next) return true;
+		nextHandlersRequired.value = ![0].includes(next.handlerType);
+	}
+);
 
 // 是否展示主办
 const showMainHandler = computed(() => {
@@ -878,10 +867,10 @@ const chooseAdviceDiscern = (item: any) => {
 const chooseAdviceRedo = (item: any) => {
 	state.redoForm.redoOpinion += item.content;
 };
-const afterSubmit = (emitType?: 'orderProcessSuccess' | 'orderProcessFailed', showMessage?: boolean,message?:string) => {
+const afterSubmit = (emitType?: 'orderProcessSuccess' | 'orderProcessFailed', showMessage?: boolean, message?: string) => {
 	state.loading = false;
 	closeDialog();
-  const msg = message ?? '操作成功';
+	const msg = message ?? '操作成功';
 	if (showMessage) ElMessage.success(msg);
 	if (emitType) emit(emitType);
 };
@@ -900,10 +889,10 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 	formEl.validate((valid: boolean) => {
 		if (!valid) return;
 		let isAuditText = '确认办理';
-    // expiredStatus 超期状态(0正常 1即将超期  2已超期)  工单流转选择“结束”节点,点击“办理”时需验证该工单是否处于已超期状态
-    if(state.orderDetail.expiredStatus === 2 && state.ruleForm.nextStepCode=== 'end'){
-      isAuditText = '该工单属于超期状态,若符合延期要求,请延期通过后办结,是否继续办理'
-    }
+		// expiredStatus 超期状态(0正常 1即将超期  2已超期)  工单流转选择“结束”节点,点击“办理”时需验证该工单是否处于已超期状态
+		if (state.orderDetail.expiredStatus === 2 && state.ruleForm.nextStepCode === 'end') {
+			isAuditText = '该工单属于超期状态,若符合延期要求,请延期通过后办结,是否继续办理';
+		}
 		ElMessageBox.confirm(`${isAuditText}?`, '提示', {
 			confirmButtonText: '确认',
 			cancelButtonText: '取消',
@@ -972,7 +961,7 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 						};
 						delayApply(requestDelay)
 							.then(() => {
-								afterSubmit('orderProcessSuccess', true,'延期申请成功');
+								afterSubmit('orderProcessSuccess', true, '延期申请成功');
 							})
 							.catch(() => {
 								afterSubmit('orderProcessFailed');
@@ -1029,7 +1018,7 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 						};
 						discernApply(requestDiscern)
 							.then(() => {
-								afterSubmit('orderProcessSuccess', true,'甄别申请成功');
+								afterSubmit('orderProcessSuccess', true, '甄别申请成功');
 							})
 							.catch(() => {
 								afterSubmit('orderProcessFailed');
@@ -1070,34 +1059,17 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 								});
 						}
 						break;
-					case '工单重办':
-						const requestRedo = {
-							data: {
-								orderId: state.orderDetail.id,
-								orderRedoReason: state.redoForm.orderRedoReason,
-								redoOpinion: state.redoForm.redoOpinion,
-							},
-							workflow: { ...submitObj, files: handleFiles.value },
-						};
-						redoApply(requestRedo)
-							.then(() => {
-								afterSubmit('orderProcessSuccess', true);
-							})
-							.catch(() => {
-								afterSubmit('orderProcessFailed');
-							});
-						break;
 					case '工单退回':
 						orderPrevious({ ...submitObj, files: handleFiles.value })
 							.then(() => {
-								afterSubmit('orderProcessSuccess', true);
+								afterSubmit('orderProcessSuccess', true, '退回申请成功');
 							})
 							.catch(() => {
 								afterSubmit('orderProcessFailed');
 							});
 						break;
 					case '工单办理': // 工单办理流程
-            orderHandle({ ...submitObj, files: handleFiles.value })
+						orderHandle({ ...submitObj, files: handleFiles.value })
 							.then(() => {
 								afterSubmit('orderProcessSuccess', true);
 							})
@@ -1112,12 +1084,38 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 						};
 						KnowledgeAdd(KnowledgeAddRequest)
 							.then(() => {
-								afterSubmit('orderProcessSuccess', true);
+								afterSubmit('orderProcessSuccess', true, '新增知识成功');
 							})
 							.catch(() => {
 								afterSubmit('orderProcessFailed');
 							});
 						break;
+					case '知识审批':
+						if (state.ruleForm.isPass) {
+							// 审批通过 下一步
+							workflowNext({ ...submitObj, files: handleFiles.value })
+								.then(() => {
+									afterSubmit('orderProcessSuccess', true);
+								})
+								.catch(() => {
+									afterSubmit('orderProcessFailed');
+								});
+						} else {
+							// 审批拒绝
+							const requestDiscernAudit = {
+								opinion: state.ruleForm.opinion,
+								workflowId: state.workflowId,
+								files: handleFiles.value,
+							};
+							workflowReject(requestDiscernAudit)
+								.then(() => {
+									afterSubmit('orderProcessSuccess', true);
+								})
+								.catch(() => {
+									afterSubmit('orderProcessFailed');
+								});
+						}
+						break;
 					case '更新新增知识':
 						const KnowledgeAddUpdateRequest = {
 							data: { ...state.orderDetail },
@@ -1151,43 +1149,26 @@ const onSubmit = (formEl: FormInstance | undefined) => {
 						};
 						KnowledgeDel(KnowledgeRemoveRequest)
 							.then(() => {
-								afterSubmit('orderProcessSuccess', true);
+								afterSubmit('orderProcessSuccess', true, '删除知识申请成功');
 							})
 							.catch(() => {
 								afterSubmit('orderProcessFailed');
 							});
 						break;
 					default: // 默认工单办理
-            orderHandle({ ...submitObj, files: handleFiles.value })
-              .then(() => {
-                afterSubmit('orderProcessSuccess', true);
-              })
-              .catch(() => {
-                afterSubmit('orderProcessFailed');
-              });
+						orderHandle({ ...submitObj, files: handleFiles.value })
+							.then(() => {
+								afterSubmit('orderProcessSuccess', true);
+							})
+							.catch(() => {
+								afterSubmit('orderProcessFailed');
+							});
 						break;
 				}
 			})
 			.catch(() => {});
 	});
 };
-// 否决
-const onReject = (formEl: FormInstance | undefined) => {
-	if (!formEl) return;
-	formEl.validate((valid: boolean) => {
-		if (!valid) return;
-		ElMessageBox.confirm(`确认审批不通过?`, '提示', {
-			confirmButtonText: '确认',
-			cancelButtonText: '取消',
-			type: 'warning',
-			draggable: true,
-			cancelButtonClass: 'default-button',
-			autofocus: false,
-		})
-			.then(() => {})
-			.catch(() => {});
-	});
-};
 // 暴露变量
 defineExpose({
 	openDialog,

+ 3 - 2
src/components/ProcessTimeLine/index.vue

@@ -6,7 +6,7 @@
         <div class="left-container" v-if="!item.parentId">
           <div class="flex">
             <div class="left-container-inner text-no-wrap" :title="item.name" :style="'background-color:var(--el-color-' + item.type + ')'">
-              {{ item.name }}
+              <TextTooltip :content="item.name" placement="top" effect="dark"/>
             </div>
             <div class="right-triangle" :style="'border-left:20px solid var(--el-color-' + item.type + ')'"></div>
           </div>
@@ -16,7 +16,7 @@
           <div class="flex">
             <div class="left-triangle" :style="'border-right:20px solid var(--el-color-' + item.type + ')'"></div>
             <div class="right-container-inner text-no-wrap" :title="item.name" :style="'background-color:var(--el-color-' + item.type + ')'">
-              {{ item.name }}
+              <TextTooltip :content="item.name" placement="top" effect="dark"/>
             </div>
           </div>
         </div>
@@ -103,6 +103,7 @@ import { formatDate } from '@/utils/formatTime';
 import { removeDuplicate } from '@/utils/arrayOperation';
 
 const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue')); // 附件列表
+const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
 const emit = defineEmits(['node-click']);
 const props = defineProps({
   // 数据

+ 62 - 0
src/components/TextTooltip/index.vue

@@ -0,0 +1,62 @@
+<template>
+	<el-tooltip
+		:content="props.tooltipContent ? props.tooltipContent : props.content"
+		:placement="props.placement"
+		:disabled="isShow"
+		:effect="props.effect"
+	>
+		<template #content>
+			<!-- 此处的默认值先看tooltipContent有没有,没有就给默认content -->
+			<slot name="tooltipContent">{{ props.tooltipContent ? props.tooltipContent : props.content }}</slot>
+		</template>
+		<div class="content" @mouseover="isShowTooltip"  :class="props.className">
+			<span ref="contentRef">
+				<!-- 给一个没有写插槽的默认值,兼容纯文本的情况 -->
+				<slot name="content">{{ props.content }}</slot>
+			</span>
+		</div>
+	</el-tooltip>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue';
+// 使用withDefaults来给props赋默认值
+const props = defineProps({
+	content: {
+		type: String,
+		default: '',
+	},
+	tooltipContent: {
+		type: String,
+		default: '',
+	},
+	effect: {
+		type: String,
+		default: 'light',
+	},
+	placement: {
+		type: String,
+		default: 'right',
+	},
+  className: {
+    type: String,
+    default: ''
+  }
+});
+// 使用isShow来控制tooltip是否显示
+let isShow = ref<boolean>(true);
+// 在span标签上定义一个ref
+const contentRef = ref();
+const isShowTooltip = function (): void {
+	// 计算span标签的offsetWidth与盒子元素的offsetWidth,给isShow赋值
+	isShow.value = contentRef.value.parentNode.offsetWidth >= contentRef.value.offsetWidth;
+};
+</script>
+<style>
+.content {
+	display: inline-block;
+	max-width: 100%;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+</style>

+ 0 - 169
src/components/Upload/index.vue

@@ -1,169 +0,0 @@
-<template>
-	<div class="upload-container w100">
-		<el-upload
-			class="upload-demo w100"
-			v-model:file-list="infoList"
-			:action="props.actionUrl"
-			:limit="props.limit"
-			:disabled="props.disabled"
-			:auto-upload="props.autoUpload"
-			:multiple="props.multiple"
-			:accept="props.accept"
-			:on-exceed="handleExceed"
-			:http-request="httpRequest"
-			:on-change="handleChange"
-			ref="uploadListRef"
-		>
-			<el-button> <SvgIcon name="ele-Upload" /> 上传附件 </el-button>
-			<slot> </slot>
-			<template #file="{ file }">
-				<div class="el-upload-list__item-info" @click="handlePictureCardPreview(file)">
-					<a class="el-upload-list__item-name">
-						<SvgIcon class="mr5" :name="fileType(checkFile(file.name))" size="14px" />
-						<span class="el-upload-list__item-file-name" :title="file.name">{{ file.name }}</span>
-					</a>
-				</div>
-				<SvgIcon name="ele-Close" class="el-icon--close" size="14px" title="删除文件" @click="handleRemove(file)" />
-			</template>
-		</el-upload>
-	</div>
-</template>
-
-<script setup lang="ts">
-/**
- * @desc 封装 element UI el-upload
- * @param {fileLists} [v-model] - 双向绑定的值
- * @param {Number} [limit] [v-bind]- 上传个数限制
- * @param {String} [accept] [v-bind]- 上传后缀的限制
- * @param {disabled} [Boolean] [v-bind]- 是否禁用
- * @example <UpLoad :limit='1' v-model:fileLists='ruleForm.fileLists' accept='image/png,image/jpg'/>
- */
-import { ref, onMounted } from 'vue';
-import { checkFile, fileType } from '@/utils/tools';
-import type { UploadUserFile } from 'element-plus';
-import { ElMessage, ElMessageBox } from 'element-plus';
-// import { uploadUrl } from '@/utils/appConfig';
-const props = defineProps({
-	limit: {
-		type: Number,
-	},
-	accept: {
-		type: String,
-		default: '',
-	},
-	fileList: {
-		type: Array,
-		default: <EmptyArrayType>[],
-		required: true,
-	},
-	disabled: {
-		type: Boolean,
-		default: false,
-	},
-	multiple: {
-		type: Boolean,
-		default: false,
-	},
-	actionUrl: {
-		type: String,
-		default: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
-	},
-	autoUpload: {
-		type: Boolean,
-		default: true,
-	},
-	// 大小限制(MB)
-	fileSize: {
-		type: Number,
-		default: 10,
-	},
-});
-
-const infoList = ref(<EmptyArrayType>[]);
-const emit = defineEmits(['update:fileList']);
-const uploadListRef = ref<RefType>();
-onMounted(() => {
-	infoList.value = props.fileList || [];
-});
-// 删除
-const handleRemove = (uploadFile: UploadUserFile) => {
-	ElMessageBox.confirm(`确定要删除?`, '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-		draggable: true,
-		cancelButtonClass: 'default-button',
-	})
-		.then(() => {
-			// infoList.value.forEach((v: any, i: any) => {
-			// 	if (v.uid == uploadFile.uid) {
-			// 		infoList.value.splice(i, 1);
-			// 	}
-			// });
-			uploadListRef.value.handleRemove(uploadFile);
-			emit('update:fileList', infoList.value);
-		})
-		.catch(() => {});
-};
-// 预览文件
-const handlePictureCardPreview = (uploadFile: UploadUserFile) => {
-	console.log(uploadFile, '121');
-};
-// 上传
-const httpRequest = async (params: any) => {
-	const file = params.file;
-	// 文件名(8位随机数):zokcutz4.jpg
-	const fileName = Math.random().toString(36).substr(-8) + file.name.substr(file.name.lastIndexOf('.'));
-
-	// 目录加文件名【上线前修改目录名】
-	const fileSrc = `test20220824/${fileName}`;
-	console.log(fileSrc);
-	try {
-		params.onProgress({ percent: 0 });
-		//上传的自定义逻辑都在这里
-
-		infoList.value = [
-			...infoList.value,
-			
-		];
-		// console.log(`上传成功 ${res.key}`)
-		console.log(infoList.value)
-		emit('update:fileList', infoList.value);
-		params.onSuccess();
-	} catch (err) {
-		console.log(err, 'err');
-
-		ElMessage.error('网络错误,请稍后重试');
-		params.onError();
-	}
-};
-//文件限制
-const handleExceed = (files: any, fileList: any) => {
-	ElMessage.warning(`当前限制选择 ${props.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
-};
-//上传前的校验
-const handleChange = (rawFile: any) => {
-	const isLt = rawFile.size / 1024 / 1024 < props.fileSize;
-	if (!isLt) {
-		ElMessage.warning(`文件超过了最大限度 ${props.fileSize} MB!`);
-		infoList.value = infoList.value.slice(0, infoList.value.length - 1);
-		return;
-	}
-};
-</script>
-
-<style scoped lang="scss">
-.upload-container {
-	.upload-demo {
-		:deep(.el-upload-list) {
-			display: flex;
-			flex-wrap: wrap;
-			.el-upload-list__item {
-				width: calc(25% - 10px);
-				border: var(--el-border);
-				margin-right: 10px;
-			}
-		}
-	}
-}
-</style>

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

@@ -67,7 +67,7 @@ onMounted(() => {
 	NextLoading.done(600);
 	// 监听页面需要滚动事件
 	mittBus.on('scrollTopEmit', (res: any) => {
-		layoutMainScrollbarRef.value?.scrollTo(res.top ? res.top : 0, res.left ? res.left : 0)// 滚动到一组特定坐标(x,y)  示例参考/componets/Pagination
+		layoutMainScrollbarRef.value?.scrollTo(res.top ? res.top : 0, res.left ? res.left : 0)// 滚动到一组特定坐标(x,y)
 	});
 });
 // 暴露变量

+ 1 - 1
src/layout/footer/footer.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="layout-footer pd15">
+  <div class="layout-footer">
     <div class="layout-footer-warp">
       <el-link href="#" target="_blank" type="info"> 成都丰窝科技有限公司 </el-link>
     </div>

+ 8 - 8
src/layout/footer/index.vue

@@ -1,7 +1,7 @@
 <template>
-	<div class="layout-footer pb15">
+	<div class="layout-footer">
 		<div class="layout-footer-warp">
-      <el-link href="#" target="_blank" type="info"> 丰窝科技❤️</el-link>
+			<el-link href="https://beian.miit.gov.cn/" target="_blank">备案号:蜀ICP备19035032号-36</el-link>
 		</div>
 	</div>
 </template>
@@ -15,12 +15,12 @@
 	width: 100%;
 	display: flex;
 	&-warp {
-    margin: auto;
-    text-align: center;
-    animation: error-num 0.3s ease;
-    :deep(.el-link){
-      //color: var(--el-text-color-secondary);
-    }
+		margin: auto;
+		text-align: center;
+		animation: error-num 0.3s ease;
+		:deep(.el-link) {
+			//color: var(--el-text-color-secondary);
+		}
 	}
 }
 </style>

+ 1 - 1
src/layout/main/columns.vue

@@ -33,7 +33,7 @@ const { themeConfig } = storeToRefs(storesThemeConfig);
 // 重置滚动条高度
 const updateScrollbar = () => {
 	// 更新父级 scrollbar
-	layoutScrollbarRef.value.update();
+	layoutScrollbarRef.value?.update();
 	// 更新子级 scrollbar
 	layoutMainRef.value.layoutMainScrollbarRef.update();
 };

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

@@ -32,7 +32,7 @@ const { themeConfig } = storeToRefs(storesThemeConfig);
 // 重置滚动条高度
 const updateScrollbar = () => {
 	// 更新父级 scrollbar
-	layoutScrollbarRef.value.update();
+	layoutScrollbarRef.value?.update();
 	// 更新子级 scrollbar
 	layoutMainRef.value.layoutMainScrollbarRef?.update();
 };
@@ -69,7 +69,7 @@ onMounted(() => {
 	NextLoading.done(600);
 	// 监听页面需要滚动事件
 	mittBus.on('scrollTopEmit', (res: any) => {
-		layoutScrollbarRef.value?.scrollTo(res.top ? res.top : 0, res.left ? res.left : 0); // 滚动到一组特定坐标(x,y)  示例参考/componets/Pagination
+		layoutScrollbarRef.value?.scrollTo(res.top ? res.top : 0, res.left ? res.left : 0); // 滚动到一组特定坐标(x,y)
 	});
 });
 </script>

+ 2 - 2
src/layout/navBars/breadcrumb/search.vue

@@ -9,7 +9,7 @@
 					ref="layoutMenuAutocompleteRef"
 					@select="onHandleSelect"
 					:fit-input-width="true"
-          value-key="path"
+					value-key="path"
 				>
 					<template #prefix>
 						<el-icon class="el-input__icon">
@@ -85,7 +85,7 @@ const createFilter: any = (queryString: string) => {
 const initTageView = () => {
 	if (state.tagsViewList.length > 0) return false;
 	tagsViewRoutes.value.map((v: any) => {
-		if (!v.meta.isHide) state.tagsViewList.push({ ...v });
+		if (!v.meta.isHide && v.permissionCode) state.tagsViewList.push({ ...v });
 	});
 };
 // 当前菜单选中时

+ 239 - 75
src/layout/navBars/breadcrumb/telControl.vue

@@ -479,7 +479,7 @@
 </template>
 
 <script setup lang="ts" name="telControl">
-import { reactive, ref, computed, defineAsyncComponent, onMounted, onUnmounted } from "vue";
+import { reactive, ref, computed, defineAsyncComponent, onMounted, onBeforeUnmount } from 'vue';
 import { ElMessageBox, ElNotification, ElMessage, FormInstance } from 'element-plus';
 import { storeToRefs } from 'pinia';
 import { useTelStatus, TelStates, RestStates } from '@/stores/telStatus';
@@ -490,16 +490,26 @@ import { formatDuration } from '@/utils/formatTime';
 import { commonEnum } from '@/utils/constants';
 import other from '@/utils/other';
 import { workflowStepOptions } from '@/api/system/workflow';
-import { restFlowStart, restFlowDel, restFlowStartWex, getTelList, telRestBaseData, dutyOff, dutyOn, busyOff, busyOn } from '@/api/public/wex';
+import {
+	restFlowStart,
+	restFlowDel,
+	restFlowStartWex,
+	getTelList,
+	telRestBaseData,
+	dutyOff,
+	dutyOn,
+	busyOff,
+	busyOn,
+	queryBlacklist,
+} from '@/api/public/wex';
 import signalR from '@/utils/signalR';
 import { Local } from '@/utils/storage';
 import { ola } from '@/utils/ola_api';
 import { useRouter } from 'vue-router';
-import dayjs from 'dayjs';
-import axios from 'axios';
 import { useSocket } from '@/utils/websocket';
 import mittBus from '@/utils/mitt';
-import { voiceAssistant } from "@/api/todo/voiceAssistant";
+import { voiceAssistant } from '@/api/todo/voiceAssistant';
+import { submitLog } from '@/api/public/log';
 // 引入组件
 const CommonAdvice = defineAsyncComponent(() => import('@/components/CommonAdvice/index.vue')); // 常用意见
 const AnnexList = defineAsyncComponent(() => import('@/components/AnnexList/index.vue'));
@@ -558,6 +568,7 @@ const state = reactive<any>({
 		// 三方会议表单
 		telNo: null, // 三方会议号码
 	},
+	whiteList: [], // 白名单
 });
 
 const useTelStatusStore = useTelStatus();
@@ -572,7 +583,7 @@ const talkTimer = ref<any>(null); // 通话时长定时器
 // 开始通话计时
 const startTime = () => {
 	let localTalkTime = Local.get('talkTime');
-	if (talkTime) {
+	if (talkTime.value) {
 		talkTime.value = Number(localTalkTime);
 		talkTimer.value = setInterval(() => {
 			talkTime.value++;
@@ -595,14 +606,11 @@ const removeTimer = () => {
 // 开始签入时长
 const onDutyTime = ref<any>(0); // 签入时长
 const onDutyTimer = ref<any>(null); // 签入时长定时器
-const startDutyTimer = (startTime?: any) => {
-	if (startTime) {
+const startDutyTimer = (second: any) => {
+	if (second) {
 		// 从后台获取签入时长
-		// 使用当前时间减去后台返回的签入时间
-		if (!startTime) startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
-		startTime = dayjs(new Date()).diff(dayjs(startTime), 'second');
-		if (startTime < 0) startTime = 0; // 防止后台返回的签入时间大于当前时间
-		onDutyTime.value = startTime;
+		if (second < 0) second = 0; // 防止后台返回的签入时间大于当前时间
+		onDutyTime.value = second;
 		onDutyTimer.value = setInterval(() => {
 			onDutyTime.value++;
 		}, 1000);
@@ -619,11 +627,11 @@ const removeTimerOnDuty = () => {
 };
 // 监听消息
 const signalRStart = async () => {
-  mittBus.on('RestApplyPass', (data) => {
-    // 小休审批通过消息
-    console.log(data, '小休审批通过消息');
-    RestApplyPassFn(data);
-  });
+	mittBus.on('RestApplyPass', (data) => {
+		// 小休审批通过消息
+		console.log(data, '小休审批通过消息');
+		RestApplyPassFn(data);
+	});
 };
 // 检查用户状态
 // 设置当前可用的按钮
@@ -734,6 +742,7 @@ const onControlClick = (val: string) => {
 
 const dutyFormRef = ref<RefType>();
 const currentTel = ref<any>({}); // 当前分机
+const isRest = ref<boolean>(false); // 是否小休
 //签入
 const onDutyFn = async () => {
 	if (AppConfigInfo.value.isNeedTelNo || AppConfigInfo.value.isTelNeedVerify) {
@@ -758,7 +767,8 @@ const onDutyFn = async () => {
 						currentTel.value.queue = res.result.queueId;
 						// 不需要选择分机号和密码 直接签入 传入默认分机号
 						websocket_connect(); //开启消息监听
-						startDutyTimer(res.result.startTime); // 开启计时 签入时长
+						startDutyTimer(res.result.second); // 开启计时 签入时长
+						isRest.value = res.result.isRest;
 						state.loading = false;
 					})
 					.catch(() => {})
@@ -815,12 +825,13 @@ const clickOnDuty = (formEl: FormInstance | undefined) => {
 					currentTel.value.queue = res.result.queueId;
 				}
 				websocket_connect(); //开启消息监听
-				startDutyTimer(res.result.startTime); // 开启计时 签入时长
+				startDutyTimer(res.result.second); // 开启计时 签入时长
+				isRest.value = res.result.isRest;
 				state.loading = false;
 				state.dutyDialogVisible = false;
 			})
 			.catch(() => {
-				dutyOff();
+				// dutyOff();
 				// 重置所有状态
 				useTelStatusStore.resetState();
 				console.log('呼叫中心:签入错误111');
@@ -850,6 +861,10 @@ const onConnect = () => {
 	ola.subscribe('ola.caller.' + currentTel.value.telNo);
 	ola.get_agent_state(currentTel.value.telNo);
 
+	pingTimer.value = setInterval(() => {
+		ola.ping();
+	}, 3000);
+
 	// ola.logout(currentTel.value.telNo); //连接之后,先登出一次,防止其他地方已经登陆
 	let array_ola_queue: EmptyArrayType = []; // 队列
 	if (currentTel.value.queue) {
@@ -860,6 +875,7 @@ const onConnect = () => {
 		ola.login(array_ola_queue, currentTel.value.telNo, { type: 'onhook' });
 		connectVoiceAssistant(currentTel.value.telNo); // 坐席助手开启
 	}
+	Local.set('telNo', currentTel.value.telNo);
 };
 // 业务系统发送消息
 const sendMsg = (msg: any) => {
@@ -867,8 +883,10 @@ const sendMsg = (msg: any) => {
 };
 const router = useRouter();
 const talkDealTimer = ref<any>(null); // 话后整理定时器
+const pingTimer = ref<any>(null); // 心跳定时器
+const call_direction = ref<any>(''); // 呼叫方向
 // 呼叫中心消息
-const onMessage = (event: any) => {
+const onMessage = async (event: any) => {
 	const data = JSON.parse(event.data);
 	if (data.event_type == 'agent_state') {
 		if (data.agent_extn) {
@@ -884,12 +902,19 @@ const onMessage = (event: any) => {
 			useTelStatusStore.setCallInfo({ telsNo: currentTel.value.telNo });
 			state.loading = true;
 			setTimeout(() => {
-				// 设置示闲状态
-				ola.go_ready();
-				console.log('呼叫中心:调用示闲');
+				console.log('isRest', `当前分机是否正在休息${isRest.value}`);
+				if (isRest.value) {
+					// 如果是小休状态
+					ola.go_break(''); //设置忙碌
+				} else {
+					// 设置示闲状态
+					ola.go_ready();
+					console.log('呼叫中心:调用示闲');
+				}
 			}, 1000);
 			console.log('呼叫中心:已签入');
 			ElMessage.success('签入成功');
+
 			sendMsg('login');
 		} else if (data.state == 'logout') {
 			// 签出
@@ -897,6 +922,8 @@ const onMessage = (event: any) => {
 			useTelStatusStore.resetState();
 			console.log('呼叫中心:已签出');
 			ElMessage.success('签出成功');
+			clearInterval(pingTimer.value); // 清除心跳定时器
+			isReconnect.value = false; // 不需要重连
 			seatAssistOff();
 		} else if (data.state == 'ready') {
 			// 结束计时
@@ -915,10 +942,10 @@ const onMessage = (event: any) => {
 			console.log('呼叫中心:示闲中');
 			sendMsg('ready');
 		} else if (data.state == 'unready') {
-      break_reason(data.private_data);
-      sendMsg('unready'); // 发送消息 业务系统消息通知
+			break_reason(data.private_data);
+			sendMsg('unready'); // 发送消息 业务系统消息通知
 
-      console.log('呼叫中心:示忙中,小休开始');
+			console.log('呼叫中心:示忙中,小休开始');
 			// 示忙中
 			useTelStatusStore.setPhoneControlState(TelStates.rest);
 			useTelStatusStore.setRest(RestStates.resting);
@@ -956,37 +983,52 @@ const onMessage = (event: any) => {
             });
       }*/
 		} else if (data.state == 'acw') {
-			// 设置分机号和坐席组
-			useTelStatusStore.setCallInfo({ telsNo: data.agent_extn });
+			console.log(call_direction.value, '呼入还是呼出');
+			if (call_direction.value === 'inbound') {
+				// 呼入需要进入话后整理
+				// 设置分机号和坐席组
+				useTelStatusStore.setCallInfo({ telsNo: data.agent_extn });
 
-			// 话后整理中
-			const time: number = AppConfigInfo.value.talkingDealTime * 1000; // 话后整理时间
-			ElNotification({
-				title: '自动开启话后整理成功',
-				message: `${time / 1000}秒后自动结束话后整理,或者手动结束话后整理`,
-				type: 'success',
-			});
-			// 设置话后整理
-			useTelStatusStore.setTalkingDeal(true);
-			// 设置话机状态 设置为话后整理中
-			useTelStatusStore.setPhoneControlState(TelStates.onTalkingDeal);
-			talkDealTimer.value = setTimeout(() => {
+				// 话后整理中
+				const time: number = AppConfigInfo.value.talkingDealTime * 1000; // 话后整理时间
+				ElNotification({
+					title: '自动开启话后整理成功',
+					message: `${time / 1000}秒后自动结束话后整理,或者手动结束话后整理`,
+					type: 'success',
+					duration: 1000 * 10,
+				});
 				// 设置话后整理
-				useTelStatusStore.setTalkingDeal(false);
-				// 设置话机状态 取消话后整理修改为空闲状态
-				useTelStatusStore.setPhoneControlState(TelStates.dutyOn);
+				useTelStatusStore.setTalkingDeal(true);
+				// 设置话机状态 设置为话后整理中
+				useTelStatusStore.setPhoneControlState(TelStates.onTalkingDeal);
+				talkDealTimer.value = setTimeout(() => {
+					// 设置话后整理
+					useTelStatusStore.setTalkingDeal(false);
+					// 设置话机状态 取消话后整理修改为空闲状态
+					useTelStatusStore.setPhoneControlState(TelStates.dutyOn);
+					ola.go_ready(); // 示闲
+					console.log('呼叫中心:调用示闲');
+					clearTimeout(talkDealTimer.value); // 清除话后整理定时器
+				}, time);
+				console.log('呼叫中心:话后整理中');
+				sendMsg('acw');
+			} else {
+				// 呼出直接调用示闲
 				ola.go_ready(); // 示闲
 				console.log('呼叫中心:调用示闲');
-				clearTimeout(talkDealTimer.value); // 清除话后整理定时器
-			}, time);
-			console.log('呼叫中心:话后整理中');
-			sendMsg('acw');
+			}
 		} else if (data.state == 'busy') {
-		} else {
 			console.log(data.state, '其他状态');
+			/*			// 设置振铃中
+			useTelStatusStore.setPhoneControlState(TelStates.ring);
+			sendMsg('busy');
+			console.log('呼叫中心:转接中....');*/
+		} else {
+			console.log(data.state, '其他状态1');
 		}
 
 		if (data.state == 'busy') {
+			call_direction.value = data.call_direction; // 保存呼叫方向
 			holdStatus(data.private_data); //处理保持
 			if (data.private_data == 'monitoring') {
 				// 三方来电振铃中
@@ -1009,6 +1051,9 @@ const onMessage = (event: any) => {
 				console.log('呼叫中心:三方来电挂断');
 			} else if (data.private_data == 'three_way_ring') {
 				// 三方通话呼出中
+				// 设置振铃中
+				useTelStatusStore.setPhoneControlState(TelStates.ring);
+				sendMsg('busy');
 				console.log('呼叫中心:三方通话呼出中');
 			} else if (data.private_data == 'three_way_answered') {
 				// 三方通话呼出接通
@@ -1037,6 +1082,8 @@ const onMessage = (event: any) => {
 						startTime();
 						// 设置电话状态 通话中
 						useTelStatusStore.setPhoneControlState(TelStates.onCall);
+
+						mittBus.emit('outboundConnect', data); // 外呼接通之后收到的消息
 						console.log('呼叫中心:呼出通话中');
 					}
 					sendMsg('busy');
@@ -1049,11 +1096,17 @@ const onMessage = (event: any) => {
 					console.log(data, '呼叫中心:来电弹单信息');
 					// 来电才展示弹屏
 					// 跳转到录入工单页面
-					if (!router.hasRoute('orderAccept')) {
-						ElMessage.warning('请先配置工单受理页面');
-						return;
+					await getWithList(); // 获取白名单列表
+					const isWhite = state.whiteList.filter((item: any) => item.phone === data.ani);
+					if (isWhite) {
+						// 如果来电电话在呼入白名单中 需要提示
+						ElNotification({
+							title: '来电提醒',
+							message: '该市民为白名单。',
+							type: 'success',
+						});
 					}
-					router.push({
+					await router.push({
 						name: 'orderAccept',
 						state: {
 							createBy: 'tel',
@@ -1072,7 +1125,6 @@ const onMessage = (event: any) => {
 					startTime();
 					// 设置电话状态 通话中
 					useTelStatusStore.setPhoneControlState(TelStates.onCall);
-
 					console.log('呼叫中心:呼入通话中');
 				}
 				sendMsg('busy');
@@ -1094,7 +1146,7 @@ const onMessage = (event: any) => {
 		//通话状态
 		// special feature, never mind
 		if (data.action == 'in') {
-			console.log('呼叫中心:呼入', data.caller.cid_number, data.caller.uuid);
+			console.log('呼叫中心:呼入', data.caller.cid_number, data.caller.iuud);
 			// ola.take_call(data.caller.uuid);
 		} else {
 			console.log('呼叫中心:呼出', data.caller.uuid);
@@ -1104,10 +1156,68 @@ const onMessage = (event: any) => {
 		// console.log('command/reply', data);
 	}
 };
+// 记录日志
+const submitLogFn = async (event: any) => {
+	const telsNo = Local.get('telNo');
+	const name: string = `分机号:${telsNo}的websocket断开链接`;
+	const remark: string = `websocket 断开: 错误code:${event.code}, 错误原因:${event.reason}, 是否正常断开:${event.wasClean}`;
+	console.log(name, remark, event);
+	const request = {
+		creationTime: new Date(),
+		name,
+		remark,
+		executeUrl: import.meta.env.VITE_CALLCENTER_SOCKET_URL,
+	};
+	try {
+		await submitLog(request);
+		Local.remove('telNo');
+	} catch (error) {
+		console.log(error);
+	}
+};
 // 呼叫中心链接关闭
-const onClose = () => {
-	console.log('呼叫中心断开链接');
-	// ElMessage.error('呼叫中心断开链接,请刷新重试');
+const isReconnect = ref(true); // 呼叫中心是否需要重连
+const onClose = async (event: any) => {
+	removeTimerOnDuty(); // 移除签入时长定时器
+	removeTimer(); // 移除通话计时器
+	clearTimeout(talkDealTimer.value); // 清除话后整理定时器
+	clearInterval(pingTimer.value); // 清除心跳定时器
+	clearInterval(onDutyTimer.value); // 清除签入时长定时器
+	clearInterval(talkTimer.value); // 清除通话时长定时器
+	console.log('呼叫中心断开链接', isReconnect.value ? '需要重连' : '不需要重连');
+	if (isReconnect.value) {
+		await reConnect(); // 重新链接呼叫中心
+		Local.set('currentTelNo', currentTel.value.telNo);
+	}
+	// 重置所有状态
+	useTelStatusStore.resetState();
+	await submitLogFn(event);
+};
+// 重新链接呼叫中心
+let reconnectAttempts = 0; // 重连次数
+let maxReconnectAttempts = 10; // 最大重连次数
+let reconnectInterval = 2; // 重连间隔
+const reConnect = async () => {
+	ElNotification({
+		title: '重连提示',
+		message: `检测到与呼叫中心链接断开,${reconnectInterval}秒后将重新链接`,
+		type: 'warning',
+		duration: reconnectInterval * 1000,
+	});
+	console.log('开始重连', `已重连${reconnectAttempts}次,最大重连次数${maxReconnectAttempts}次,重连间隔${reconnectInterval}秒`);
+	if (reconnectAttempts < maxReconnectAttempts) {
+		setTimeout(() => {
+			reconnectAttempts++;
+			websocket_connect();
+		}, reconnectInterval * 1000);
+	} else {
+		ElNotification({
+			title: '呼叫中心重连失败',
+			message: '已到达重连次数最高,请手动刷新重连',
+			type: 'warning',
+		});
+		console.error('已到达重连次数最高,请手动刷新重连');
+	}
 };
 // 小休原因
 const restReason = ref(''); // 小休原因
@@ -1132,16 +1242,20 @@ const break_reason = (reason: string) => {
 			restReason.value = '休息中';
 			break;
 	}
-	if (telStatusInfo.value.isRest !== 'resting') {
+	if (telStatusInfo.value.isRest !== 'resting' && !isRest.value) {
 		// 如果不在在小休中
 		const restReasons = state.restReasonOptions.find((item: any) => item.dicDataValue === reason);
 		busyOn({ reason: restReasons?.dicDataName ?? '' }) // 开始小休 设置示忙 业务系统统计需要
 			.then(() => {
 				console.log('业务系统调用示忙成功');
+				state.loading = false;
 			})
 			.catch((err) => {
 				console.log('业务系统调用示忙失败', err);
+				state.loading = false;
 			});
+	} else {
+		state.loading = false;
 	}
 };
 // 保持状态处理
@@ -1179,6 +1293,8 @@ const offDutyFn = () => {
 			state.loading = true;
 			dutyOff()
 				.then(() => {
+					console.log('业务系统:签出成功');
+					Local.set('isReconnect1', false);
 					sendMsg('logout');
 					ola.logout(currentTel.value.telNo); //签出
 					setTimeout(() => {
@@ -1186,8 +1302,13 @@ const offDutyFn = () => {
 					}, 500);
 					// 重置所有状态
 					useTelStatusStore.resetState();
-					removeTimerOnDuty(); // 移除定时器
-					state.loading = false;
+					removeTimerOnDuty(); // 移除签入时长定时器
+					removeTimer(); // 移除通话计时器
+					clearTimeout(talkDealTimer.value); // 清除话后整理定时器
+					clearInterval(pingTimer.value); // 清除心跳定时器
+					clearInterval(onDutyTimer.value); // 清除签入时长定时器
+					clearInterval(talkTimer.value); // 清除通话时长定时器
+
 					state.dutyOnSrc = getImageUrl('phoneControls/dutyOn_blue.png'); //签入图片
 					state.loading = false;
 				})
@@ -1300,6 +1421,7 @@ const clickOnRest = (formEl: FormInstance | undefined) => {
 		} else {
 			//不需要审核直接开始小休
 			ola.go_break(state.restForm.reason); //设置忙碌
+			clearTimeout(talkDealTimer.value); // 清除话后整理定时器
 			console.log('呼叫中心:调用示忙');
 			state.restDialogVisible = false;
 			state.loading = false;
@@ -1427,6 +1549,12 @@ const clickOnThreeWay = (formEl: FormInstance | undefined) => {
 	formEl.validate((valid: boolean) => {
 		if (!valid) return;
 		ola.three_way(telStatusInfo.value.telsNo, state.threeWayForm.telNo); //三方会议
+
+		// 三方通话呼出接通
+		onCallArr.value.push({ telNo: state.threeWayForm.telNo }); // 三方通话呼出接通
+		// 设置电话状态 呼出三方通话中 可以踢人和挂断通话
+		useTelStatusStore.setPhoneControlState(TelStates.onThreeWay);
+		sendMsg('treeWay');
 		state.threeWayDialogVisible = false;
 	});
 };
@@ -1441,6 +1569,8 @@ const kickOut = (item: any) => {
 	})
 		.then(() => {
 			ola.exit_three_way(item.telNo); // 踢出第三方
+			// 恢复通话
+			useTelStatusStore.setPhoneControlState(TelStates.onCall);
 			onCallArr.value = [];
 		})
 		.catch(() => {});
@@ -1481,13 +1611,13 @@ const connectVoiceAssistant = async (telNo: string) => {
 		socket.value = useSocket(import.meta.env.VITE_VOICE_ASSISTANT_SOCKET_URL, { uid, subscribe });
 		socket.value.on('open', () => {
 			socket.value.send({ id: '', type: 1, from: uid, to: 'sys', timestamps: new Date().getTime(), body: result.userName });
-      console.log('坐席辅助连接成功')
-      ElMessage.success('坐席辅助连接成功');
+			console.log('坐席辅助连接成功');
+			ElMessage.success('坐席辅助连接成功');
 		});
 		socket.value.on('message', wsReceive);
 	} catch (err) {
 		console.log(err, '坐席辅助链接失败');
-    ElMessage.error('坐席辅助链接失败');
+		ElMessage.error('坐席辅助链接失败');
 	}
 };
 // 坐席辅助关闭
@@ -1513,18 +1643,39 @@ const wsReceive = (message: any) => {
 };
 // 刷新页面呼叫中心链接
 const callCenterConnect = async () => {
-	if (telStatusInfo.value.telsNo) {
+	const currentTelNo = Local.get('currentTelNo');
+	if (currentTelNo && isReconnect.value) {
+		// 重新链接
+		try {
+			const { result } = await dutyOn({ telNo: currentTelNo });
+			currentTel.value.password = result.telPwd;
+			currentTel.value.telNo = result.telNo;
+			currentTel.value.queue = result.queueId;
+			Local.remove('currentTelNo');
+			startDutyTimer(result.second); // 开启计时 签入时长
+			isRest.value = result.isRest;
+			state.loading = false;
+		} catch (e) {
+			console.log(e);
+			// await dutyOff();
+			// 重置所有状态
+			useTelStatusStore.resetState();
+			state.loading = false;
+		}
+	} else if (telStatusInfo.value.telsNo) {
+		// 刷新页面
 		try {
 			const { result } = await dutyOn({ telNo: telStatusInfo.value.telsNo });
 			currentTel.value.password = result.telPwd;
 			currentTel.value.telNo = result.telNo;
 			currentTel.value.queue = result.queueId;
 			websocket_connect(); //开启消息监听
-			startDutyTimer(result.startTime); // 开启计时 签入时长
+			startDutyTimer(result.second); // 开启计时 签入时长
+			isRest.value = result.isRest;
 			state.loading = false;
 		} catch (e) {
 			console.log(e);
-			await dutyOff();
+			// await dutyOff();
 			// 重置所有状态
 			useTelStatusStore.resetState();
 			state.loading = false;
@@ -1544,20 +1695,33 @@ const getReason = async () => {
 		console.log(err);
 	}
 };
+// 获取白名单列表
+const getWithList = async () => {
+	try {
+		const response: any = await queryBlacklist({ SpecialFlag: 1 });
+		state.whiteList = response.result;
+	} catch (err) {
+		console.log(err);
+	}
+};
 onMounted(async () => {
-	clearTimeout(talkDealTimer.value); // 清除话后整理定时器
 	await getReason(); // 获取小休原因
 	await signalRStart(); //开启消息监听
-	removeTimerOnDuty(); // 移除定时器
+	removeTimerOnDuty(); // 移除签入时长定时器
+	removeTimer(); // 移除通话计时器
+	clearTimeout(talkDealTimer.value); // 清除话后整理定时器
+	clearInterval(pingTimer.value); // 清除心跳定时器
+	clearInterval(onDutyTimer.value); // 清除签入时长定时器
+	clearInterval(talkTimer.value); // 清除通话时长定时器
 	await getTelsLists(); // 查询所有分机
 	await callCenterConnect(); // 呼叫中心链接
-
 	// 加入分组
-	signalR.joinGroup('CallCenter');
+	await signalR.joinGroup('CallCenter');
+});
+onBeforeUnmount(() => {
+	mittBus.off('RestApplyPass');
+	if (ola.ws) ola.close();
 });
-onUnmounted(()=>{
-  mittBus.off('RestApplyPass');
-})
 </script>
 
 <style scoped lang="scss">

+ 1 - 1
src/layout/navBars/breadcrumb/user.vue

@@ -167,7 +167,7 @@ import { changePwd } from '@/api/login/user';
 import { megcount } from '@/api/auxiliary/notice';
 import signalR from '@/utils/signalR';
 import { ola } from '@/utils/ola_api';
-import { busyOff, dutyOff } from '@/api/public/wex';
+import { dutyOff } from '@/api/public/wex';
 
 // 引入组件
 const UserNews = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/userNews.vue'));

+ 0 - 4
src/layout/navBars/breadcrumb/userNews.vue

@@ -49,10 +49,6 @@ const getNumAndList = async () => {
 // 点击消息通知
 const router = useRouter();
 const linkNews = (v: any) => {
-	if (!router.hasRoute('auxiliaryNoticeRead')) {
-		ElMessage.warning('未找到通知详情页面');
-		return;
-	}
 	emit('hideNws');
 	router.push({
 		name: 'auxiliaryNoticeRead',

+ 4 - 3
src/layout/navBars/tagsView/tagsView.vue

@@ -264,8 +264,9 @@ const closeCurrentTagsView = (path: string) => {
 	state.tagsViewList.map((v: RouteItem, k: number, arr: any) => {
 		if (!v.meta?.isAffix) {
 			if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
-				storesKeepALiveNames.delCachedView(v);
 				state.tagsViewList.splice(k, 1);
+        const fi = state.tagsViewList.filter((i: any) => i.name === v.name);
+        if(!fi) storesKeepALiveNames.delCachedView(v); // 动态路由会有多个 如果有多个不清除缓存
 				setTimeout(() => {
 					if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
 						// 最后一个且高亮时
@@ -492,7 +493,7 @@ const tagsViewmoveToCurrentTag = () => {
 			}
 		}
 		// 更新滚动条,防止不出现
-		scrollbarRef.value.update();
+		scrollbarRef.value?.update();
 	});
 };
 // 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
@@ -590,7 +591,7 @@ onMounted(() => {
 // 路由更新时(组件内生命钩子)
 onBeforeRouteUpdate(async (to) => {
 	state.routeActive = setTagsViewHighlight(to);
-	state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
+	state.routePath = to.meta?.isDynamic ? to.meta?.isDynamicPath : to.path;
 	await addTagsView(to.path, <any>to);
 	getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
 });

+ 5 - 4
src/layout/navMenu/horizontal.vue

@@ -6,8 +6,8 @@
 				<template v-for="val in menuLists">
 					<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
 						<template #title>
-							<SvgIcon :name="val.meta.icon" />
-							<span>{{ val.meta.title }}</span>
+							<SvgIcon :name="val.meta.icon" />\
+              <TextTooltip :content="val.meta.title" />
 						</template>
 						<SubItem :chil="val.children" />
 					</el-sub-menu>
@@ -15,12 +15,12 @@
 						<el-menu-item :index="val.path" :key="val.path">
 							<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
 								<SvgIcon :name="val.meta.icon" />
-								{{ val.meta.title }}
+                <TextTooltip :content="val.meta.title" />
 							</template>
 							<template #title v-else>
 								<a class="w100" @click.prevent="onALinkClick(val)">
 									<SvgIcon :name="val.meta.icon" />
-									{{ val.meta.title }}
+                  <TextTooltip :content="val.meta.title" />
 								</a>
 							</template>
 						</el-menu-item>
@@ -42,6 +42,7 @@ import mittBus from '@/utils/mitt';
 
 // 引入组件
 const SubItem = defineAsyncComponent(() => import('@/layout/navMenu/subItem.vue'));
+const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
 // 定义父组件传过来的值
 const props = defineProps({
 	// 菜单列表

+ 4 - 3
src/layout/navMenu/subItem.vue

@@ -18,7 +18,7 @@
      <el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
 			<template #title>
 				<SvgIcon :name="val.meta.icon" />
-				<span>{{ val.meta.title }}</span>
+        <TextTooltip :content="val.meta.title" />
 			</template>
 			<sub-item :chil="val.children" />
 		</el-sub-menu>
@@ -26,12 +26,12 @@
 			<el-menu-item :index="val.path" :key="val.path">
 				<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
 					<SvgIcon :name="val.meta.icon" />
-					<span>{{ val.meta.title }}</span>
+          <TextTooltip :content="val.meta.title" />
 				</template>
 				<template v-else>
 					<a class="w100" @click.prevent="onALinkClick(val)">
 						<SvgIcon :name="val.meta.icon" />
-						{{ val.meta.title }}
+            <TextTooltip :content="val.meta.title" />
 					</a>
 				</template>
 			</el-menu-item>
@@ -46,6 +46,7 @@ import { verifyUrl } from '@/utils/toolsValidate';
 import other from '@/utils/other';
 // 引入组件
 const SubThreeItem = defineAsyncComponent(() => import('@/layout/navMenu/subThreeItem.vue'));
+const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
 // 定义父组件传过来的值
 const props = defineProps({
 	chil: {

+ 4 - 3
src/layout/navMenu/subThreeItem.vue

@@ -18,13 +18,13 @@
 				<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
 					<div @click="pushRouter(val)" class="path-inner">
 						<SvgIcon :name="val.meta.icon" v-if="val.meta?.icon" class="icons" />
-						<span :title="val.meta.title" class="text-no-wrap">{{ val.meta.title }}</span>
+            <TextTooltip :content="val.meta.title" />
 					</div>
 				</template>
 				<template v-else>
 					<a class="w100 path-inner" @click.prevent="onALinkClick(val)">
 						<SvgIcon :name="val.meta.icon" />
-						{{ val.meta.title }}
+            <TextTooltip :content="val.meta.title" />
 					</a>
 				</template>
 			</div>
@@ -33,12 +33,13 @@
 </template>
 
 <script setup lang="ts" name="navMenuSubItemThree">
-import { computed, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, ref, watch } from "vue";
 import { useRouter, useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '@/stores/themeConfig';
 import { verifyUrl } from '@/utils/toolsValidate';
 
+const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
 // 定义父组件传过来的值
 const props = defineProps({
 	chil: {

+ 3 - 2
src/layout/navMenu/vertical.vue

@@ -11,7 +11,7 @@
 			<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
 				<template #title>
 					<SvgIcon :name="val.meta.icon" />
-					<span>{{ val.meta.title }}</span>
+          <TextTooltip :content="val.meta.title" />
 				</template>
 				<SubItem :chil="val.children" />
 			</el-sub-menu>
@@ -19,7 +19,7 @@
 				<el-menu-item :index="val.path" :key="val.path">
 					<SvgIcon :name="val.meta.icon" />
 					<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
-						<span>{{ val.meta.title }}</span>
+            <TextTooltip :content="val.meta.title" />
 					</template>
 					<template #title v-else>
 						<a class="w100" @click.prevent="onALinkClick(val)">{{ val.meta.title }}</a>
@@ -39,6 +39,7 @@ import { verifyUrl } from '@/utils/toolsValidate';
 
 // 引入组件
 const SubItem = defineAsyncComponent(() => import('@/layout/navMenu/subItem.vue'));
+const TextTooltip = defineAsyncComponent(() => import('@/components/TextTooltip/index.vue'));
 
 // 定义父组件传过来的值
 const props = defineProps({

+ 4 - 4
src/layout/routerView/parent.vue

@@ -34,8 +34,8 @@ const storesThemeConfig = useThemeConfig();
 const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
 const { themeConfig } = storeToRefs(storesThemeConfig);
 const state = reactive<ParentViewState>({
-	refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
-	iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
+	refreshRouterViewKey: null, // 非 iframe tagsview 右键菜单刷新时
+	iframeRefreshKey: null, // iframe tagsview 右键菜单刷新时
 	keepAliveNameList: [],
 	iframeList: [],
 });
@@ -66,8 +66,8 @@ onBeforeMount(() => {
 	state.keepAliveNameList = keepAliveNames.value;
 	mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
 		state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
-		state.refreshRouterViewKey = '';
-		state.iframeRefreshKey = '';
+		state.refreshRouterViewKey = null;
+		state.iframeRefreshKey = null;
 		nextTick(() => {
 			state.refreshRouterViewKey = fullPath;
 			state.iframeRefreshKey = fullPath;

+ 4 - 3
src/main.ts

@@ -8,21 +8,22 @@ import other from '@/utils/other';
 import ElementPlus from 'element-plus';
 import 'element-plus/dist/index.css';
 import '@/theme/index.scss';
-// 分页组件
-import Pagination from '@/components/Pagination/index.vue';
 // 空组件
 import Empty from '@/components/Empty/index.vue';
 // 表格组件
 import ProTable from '@/components/ProTable/index.vue';
 import { MotionPlugin } from '@vueuse/motion';
 
+// 注册echarts
+import { registerEcharts } from '@/utils/echarts';
+
 const app = createApp(App);
 
 // 自定义指令和svg组件
 directive(app);
 other.elSvg(app);
 // 全局组件挂载
-app.component('Pagination', Pagination);
 app.component('Empty', Empty);
 app.component('ProTable', ProTable);
+registerEcharts(app);
 app.use(pinia).use(router).use(ElementPlus).use(MotionPlugin).mount('#app');

+ 7 - 1
src/router/backEnd.ts

@@ -58,9 +58,15 @@ const getAppConfigFn = async () => {
 			talkingDealTime: result.talkingDealTime ?? 0, // 自动话后整理时间
 			isTelNeedVerify: result.isTelNeedVerify ?? false, //分机签入是否需要输入密码
 			isCustomEvent: result.isCustomEvent ?? false, //是否开启自定义事件
+			isTranspondCity: result.isTranspondCity ?? false, //是否开启市州互转
+			isAverageSendOrder: result.isAverageSendOrder ?? false, //是否开启平均派单
+			isOpenJudicialManagement: result.isOpenJudicialManagement ?? false, //是否开启司法行政执法工单选项
 		});
 		console.log(
-			`是否开启小休审批${result.isRestApproval},自动话后整理时间${result.talkingDealTime}秒,分机签入是否需要选择号码${result.isNeedTelNo},分机签入是否需要输入密码${result.isTelNeedVerify},是否开启自定义事件${result.isCustomEvent}`
+			`是否开启小休审批${result.isRestApproval},自动话后整理时间${result.talkingDealTime}秒,
+			分机签入是否需要选择号码${result.isNeedTelNo},分机签入是否需要输入密码${result.isTelNeedVerify},
+			是否开启自定义事件${result.isCustomEvent},是否开启市州互转${result.isTranspondCity},是否开启平均派单${result.isAverageSendOrder},
+			是否开启司法行政执法工单选项${result.isOpenJudicialManagement}`
 		);
 	} catch (e) {
 		console.log(e);

+ 209 - 0
src/router/route.ts

@@ -63,6 +63,215 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
 			},
 		],
 	},
+	{
+		path: '/todo/seats/accept/:tagsViewName/:callId?/:id?',
+		name: 'orderAccept',
+		component: () => import('@/views/todo/seats/accept/index.vue'),
+		meta: {
+			title: '工单受理',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/knowledge/index/edit/:id?/:tagsViewName/:isDraft?',
+		name: 'knowledgeEdit',
+		component: () => import('@/views/knowledge/index/edit.vue'),
+		meta: {
+			title: '知识库新增/编辑',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/knowledge/index/preview/:tagsViewName/:id?/:isAddPv?',
+		name: 'knowledgePreview',
+		component: () => import('@/views/knowledge/index/preview.vue'),
+		meta: {
+			title: '知识查看',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/system/roles/dataAuth/:id/:tagsViewName?',
+		name: 'systemDataAuth',
+		component: () => import('@/views/system/dataAuth/index.vue'),
+		meta: {
+			title: '数据权限',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/system/config/workflow/edit/:id?/:tagsViewName',
+		name: 'workflowAddEdit',
+		component: () => import('@/views/system/config/workflow/component/workflowEdit.vue'),
+		meta: {
+			title: '流程编辑',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/public/notice/:tagsViewName/:id/:isRead',
+		name: 'auxiliaryNoticeRead',
+		component: () => import('@/views/public/notice/index.vue'),
+		meta: {
+			title: '通知阅读',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/order/visitTable/:id/:tagsViewName?',
+		name: 'statisticsOrderVisitTable',
+		component: () => import('@/views/statistics/order/visitTable.vue'),
+		meta: {
+			title: '回访不满意原因统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/order/specialTable/:cause/:tagsViewName?',
+		name: 'statisticsOrderSpecialTable',
+		component: () => import('@/views/statistics/order/specialTable.vue'),
+		meta: {
+			title: '特提统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/department/Detail/:key/:tagsViewName?',
+		name: 'statisticsDepartmentSatisfiedDetail',
+		component: () => import('@/views/statistics/department/detailSatisfied.vue'),
+		meta: {
+			title: '部门满意度统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/detailSatisfiedOrg/org/:key/:tagsViewName?',
+		name: 'statisticsDepartmentSatisfiedOrg',
+		component: () => import('@/views/statistics/department/detailSatisfiedOrg.vue'),
+		meta: {
+			title: '部门满意度统计明细部门',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/center/detailTelFrequently/:FromPhone/:tagsViewName?',
+		name: 'telFrequentlyDetail',
+		component: () => import('@/views/statistics/center/detailTelFrequently.vue'),
+		meta: {
+			title: '高频来电统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/center/detailEventFrequently/:id/:tagsViewName?',
+		name: 'eventFrequentlyDetail',
+		component: () => import('@/views/statistics/center/detailEventFrequently.vue'),
+		meta: {
+			title: '高频事项预警统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/department/detailOverdue/:id/:tagsViewName?',
+		name: 'statisticsDepartmentOverdueDetail',
+		component: () => import('@/views/statistics/department/detailOverdue.vue'),
+		meta: {
+			title: '部门超期统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailEventClass/:id/:tagsViewName?',
+		name: 'judicialDetailEventClass',
+		component: () => import('@/views/judicial/statistics/detailEventClass.vue'),
+		meta: {
+			title: '事项分类统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailDepartmentSub/:id/:tagsViewName?', // 部门
+		name: 'judicialStatisticsDepartmentSub',
+		component: () => import('@/views/judicial/statistics/detailDepartmentSub.vue'),
+		meta: {
+			title: '执法部门办件统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailDepartment/:id/:tagsViewName?', //工单
+		name: 'judicialDetailDepartment',
+		component: () => import('@/views/judicial/statistics/detailDepartment.vue'),
+		meta: {
+			title: '执法部门办件统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailArea/:id/:tagsViewName?',
+		name: 'judicialDetailArea',
+		component: () => import('@/views/judicial/statistics/detailArea.vue'),
+		meta: {
+			title: '区域分类统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailSatisfied/:id/:tagsViewName?',
+		name: 'judicialStatisticsDetailSatisfied',
+		component: () => import('@/views/judicial/statistics/detailSatisfied.vue'),
+		meta: {
+			title: '部门满意度统计明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/judicial/statistics/detailSatisfiedOrg/:id/:tagsViewName?',
+		name: 'judicialStatisticsDetailSatisfiedOrg',
+		component: () => import('@/views/judicial/statistics/detailSatisfiedOrg.vue'),
+		meta: {
+			title: '部门满意度统计明细部门',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
+	{
+		path: '/statistics/center/detailWrongItem',
+		name: 'statisticsCenterDetailWrongItem',
+		component: () => import('@/views/statistics/center/detailWrongItem.vue'),
+		meta: {
+			title: '回退错件统计明细',
+			isKeepAlive: true,
+		},
+	},
+	{
+		path: '/statistics/order/detailAcceptType/:id/:tagsViewName?',
+		name: 'statisticsDetailAcceptType',
+		component: () => import('@/views/statistics/order/detailAcceptType.vue'),
+		meta: {
+			title: '部门受理类型统计周期明细',
+			isKeepAlive: true,
+			isDynamic:true
+		},
+	},
 ];
 
 /**

+ 3 - 0
src/stores/appConfig.ts

@@ -12,6 +12,9 @@ export const useAppConfig = defineStore('AppConfig', {
 			isNeedTelNo: false, // 分机签入是否需要选择号码
 			isTelNeedVerify: false, // 分机签入是否需要输入密码
 			isCustomEvent: false, // 是否开启自定义事件
+			isTranspondCity:false, // 是否开启市州互转
+			isAverageSendOrder:false, // 是否开启平均派单
+			isOpenJudicialManagement:false, // 是否开启司法管理
 		},
 	}),
 	actions: {

+ 1 - 1
src/stores/keepAliveNames.ts

@@ -23,7 +23,7 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
 		},
 		async delCachedView(view: any) {
 			const index = this.cachedViews?.indexOf(view.name);
-			index > -1 && this.cachedViews.splice(index, 1);
+			index > -1 && this.cachedViews?.splice(index, 1);
 		},
 		async delOthersCachedViews(view: any) {
 			if (view.meta.isKeepAlive) this.cachedViews = [view.name];

+ 1 - 1
src/stores/themeConfig.ts

@@ -89,7 +89,7 @@ export const useThemeConfig = defineStore('themeConfig', {
 			// 是否开启 TagsView 共用
 			isShareTagsView: false,
 			// 是否开启 Footer 底部版权信息
-			isFooter: false,
+			isFooter: true,
 			// 是否开启灰色模式
 			isGrayscale: false,
 			// 是否开启色弱模式

+ 11 - 8
src/stores/userInfo.ts

@@ -24,6 +24,7 @@ export const useUserInfo = defineStore('userInfo', {
 			orgName:'', // 组织名称
 			roles: [], // 角色
 			isCenter: false, // 当前本部门是否是中心
+			monitor: false, // 是否是班长
 		},
 	}),
 	actions: {
@@ -38,17 +39,18 @@ export const useUserInfo = defineStore('userInfo', {
 			try {
 				// 个人信息
 				let userInfo: any = await getUserInfo();
-				this.userInfos.name = userInfo.result.name ?? '暂无名称';
-				this.userInfos.account = userInfo.result.account ?? '';
-				this.userInfos.phoneNo = userInfo.result.phoneNo ?? '';
-				this.userInfos.staffNo = userInfo.result.staffNo ?? '';
+				this.userInfos.name = userInfo.result?.user.name ?? '暂无名称';
+				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.id = userInfo.result.id ?? '';
-				this.userInfos.roles = userInfo.result.roles ?? [];
+				this.userInfos.id = userInfo.result?.user.id ?? '';
+				this.userInfos.roles = userInfo.result?.user.roles ?? [];
 				this.userInfos.token = Cookie.get('token') ?? '';
 				this.userInfos.photo = "";
-				this.userInfos.orgName = userInfo.result.organization?.name ?? '';
-				this.userInfos.isCenter = userInfo.result.organization?.isCenter ?? false;
+				this.userInfos.orgName = userInfo.result?.user.organization?.name ?? '';
+				this.userInfos.isCenter = userInfo.result?.user.organization?.isCenter ?? false;
+				this.userInfos.monitor = userInfo.result?.monitor ?? false;
 				//授权按钮
 				this.userInfos.showTelControl = buttons.includes('public:seat:panel'); // 查询是否有展示面板权限
 				this.userInfos.authBtnList = buttons;
@@ -77,6 +79,7 @@ export const useUserInfo = defineStore('userInfo', {
 					orgName:'', // 组织名称
 					roles: [], // 角色
 					isCenter: false, // 当前部门是否是中心
+					monitor: false, // 是否是班长
 				}
 				Session.set('userInfo', this.userInfos);
 				return this.userInfos;

+ 24 - 2
src/theme/element.scss

@@ -63,7 +63,7 @@
 	height: 46px !important;
 	line-height: 46px !important;
 }
-.el-sub-menu__title{
+.el-sub-menu__title {
 	height: 46px !important;
 	line-height: 46px !important;
 }
@@ -275,7 +275,7 @@
 	th.el-table__cell {
 		background-color: var(--hotline-bg-main-color) !important;
 	}
-	thead{
+	thead {
 		--el-table-border-color: var(--el-border-color-light);
 		--el-table-border: 1px solid var(--el-table-border-color);
 	}
@@ -293,6 +293,10 @@
 .el-scrollbar__wrap {
 	max-height: 100%;
 }
+.el-scrollbar__thumb {
+	//可设置滚动条颜色
+	background: #555; //这里我设置成了透明色,可以根据需求添加自己想要的颜色
+}
 .el-select-dropdown .el-scrollbar__wrap {
 	overflow-x: scroll !important;
 }
@@ -360,3 +364,21 @@
 	--el-select-width: 220px;
 }
 */
+// hover颜色样式加深
+.el-cascader-node:not(.is-disabled):focus,
+.el-cascader-node:not(.is-disabled):hover {
+	background: var(--el-fill-color-darker);
+}
+.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
+	background: var(--el-color-primary-light-7);
+}
+.el-tree-node__content:hover {
+	background: var(--el-fill-color-darker);
+}
+.el-select-dropdown__item.hover,
+.el-select-dropdown__item:hover {
+	background: var(--el-fill-color-darker);
+}
+.el-table__body tr.hover-row.current-row>td.el-table__cell, .el-table__body tr.hover-row.el-table__row--striped.current-row>td.el-table__cell, .el-table__body tr.hover-row.el-table__row--striped>td.el-table__cell, .el-table__body tr.hover-row>td.el-table__cell{
+	background: var(--el-fill-color-darker);
+}

+ 2 - 0
src/theme/splitpanes.scss

@@ -6,6 +6,8 @@
 	background-color: var(--el-border-color);
 	position: relative;
 	margin:0 10px;
+	width: 1px;
+	cursor: col-resize;
 }
 .splitpanes__splitter:before {
 	content: '';

+ 2 - 2
src/types/layout.d.ts

@@ -40,8 +40,8 @@ declare type TagsViewState<T = any> = {
 
 // navBars parent
 declare type ParentViewState<T = any> = {
-	refreshRouterViewKey: string;
-	iframeRefreshKey: string;
+	refreshRouterViewKey: string|null;
+	iframeRefreshKey: string|null;
 	keepAliveNameList: string[];
 	iframeList: T[];
 };

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

@@ -16,4 +16,5 @@ declare type MittType = {
 	CircularRecord?: any; //小红点消息通知
 	SeatState?: any; //坐席监控
 	RestApplyPass?: any; //休息申请
+	outboundConnect?: any; //外呼连接
 };

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

@@ -18,6 +18,7 @@ declare interface UserInfosState {
 	roles: string[];
 	account: string;
 	isCenter: boolean;
+	monitor: boolean;
 }
 declare interface UserInfosStates {
 	userInfos: UserInfosState;
@@ -105,6 +106,9 @@ declare interface AppConfigState {
 		isNeedTelNo: boolean; // 分机签入是否需要选择号码
 		isTelNeedVerify: boolean; // 分机签入是否需要输入密码
 		isCustomEvent: boolean; // 是否开启自定义事件
+		isTranspondCity: boolean; // 是否开启市州互转
+		isAverageSendOrder: boolean; // 是否开启平均派单
+		isOpenJudicialManagement: boolean; // 是否开启司法管理
 		[x: string]: any
 	}
 }

+ 29 - 28
src/utils/checkUpdate.tsx

@@ -1,4 +1,4 @@
-import { ElNotification,ElButton } from 'element-plus';
+import { ElNotification, ElButton } from 'element-plus';
 /**
  * @description 用于记录时间戳的变量,时间戳是响应头中的etag和last-modified字段其中之一
  */
@@ -24,33 +24,34 @@ async function judge() {
 	const currentTimeTag = await getTimeTag();
 	// 检测到最新请求的时间戳和上一次不一致,即文件发生变化
 	if (previousTimeTag !== currentTimeTag) {
-	// 需要更新的逻辑
-	// 监听是否更新
-	// @ts-ignore'
-	const messageContent = ()=>{
-		return (
-			<div>
-				<p style="justify-content:flex-end">
-					<span class='mb10'>检测到新版本,是否刷新页面?</span>
-					<div class="mt10">
-						<ElButton onClick={ignore} size="small">
-							忽略
-						</ElButton>
-						<ElButton type="primary" onClick={Refresh}  size="small">
-							刷新
-						</ElButton>
-					</div>
-				</p>
-			</div>
-		)
-	}
-	notice = ElNotification({
-		title: '提示',
-		type: 'warning',
-		dangerouslyUseHTMLString: true,
-		message: messageContent(),
-		customClass: 'updateTips',
-	});
+		// 需要更新的逻辑
+		// 监听是否更新
+		// @ts-ignore'
+		const messageContent = () => {
+			return (
+				<div>
+					<p style="justify-content:flex-end">
+						<span class="mb10">检测到前端发布了新版本,是否刷新页面?</span>
+						<div class="mt10">
+							<ElButton onClick={ignore} size="small">
+								忽略
+							</ElButton>
+							<ElButton type="primary" onClick={Refresh} size="small">
+								刷新
+							</ElButton>
+						</div>
+					</p>
+				</div>
+			);
+		};
+		notice = ElNotification({
+			title: '提示',
+			type: 'warning',
+			dangerouslyUseHTMLString: true,
+			message: messageContent(),
+			customClass: 'updateTips',
+			duration: 50 * 1000,
+		});
 	}
 }
 /**

+ 24 - 8
src/utils/constants.ts

@@ -46,12 +46,19 @@ export const shortcuts = [
 		text: '上季度',
 		value: () => {
 			let oDate = new Date();
-			let thisYear = oDate.getFullYear();
-			let thisMonth = oDate.getMonth() + 1;
-			let n = Math.ceil(thisMonth / 3); // 季度
-			let Month = n * 3 - 1;
-			let start = new Date(thisYear, Month - 2, 1);
-			let end = new Date();
+			let prevMonth = oDate.getMonth() - 3;
+			const end = new Date();
+			const start = new Date();
+			start.setMonth(prevMonth);
+			start.setDate(1);
+			start.setHours(0);
+			start.setMinutes(0);
+			start.setSeconds(0);
+			end.setMonth(prevMonth + 3);
+			end.setDate(0);
+			end.setHours(23);
+			end.setMinutes(59);
+			end.setSeconds(59);
 			return [start, end];
 		},
 	},
@@ -60,7 +67,16 @@ export const shortcuts = [
 		value: () => {
 			const end = new Date();
 			const start = new Date();
-			start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+			start.setMonth(Math.floor(new Date().getMonth() / 3) * 3);
+			start.setDate(1);
+			start.setHours(0);
+			start.setMinutes(0);
+			start.setSeconds(0);
+			end.setMonth(Math.floor(new Date().getMonth() / 3) * 3 + 3);
+			end.setDate(0);
+			end.setHours(23);
+			end.setMinutes(59);
+			end.setSeconds(59);
 			return [start, end];
 		},
 	},
@@ -127,4 +143,4 @@ export const commonEnum = {
 // 限制日期选择最大时间
 export const disabledDate = (time: Date) => {
 	return time.getTime() < new Date().getTime();
-};
+};

+ 37 - 0
src/utils/echarts.ts

@@ -0,0 +1,37 @@
+import ECharts from 'vue-echarts'
+import {use} from "echarts/core"
+import {
+  CanvasRenderer
+} from 'echarts/renderers'
+import {
+  BarChart, PieChart, MapChart, EffectScatterChart, LineChart, PictorialBarChart, GraphChart, GaugeChart, ScatterChart
+} from 'echarts/charts'
+import {
+  GridComponent,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  DatasetComponent,
+  VisualMapComponent,
+  GeoComponent,
+  MarkPointComponent,
+  GraphicComponent,
+} from 'echarts/components'
+
+use([
+  CanvasRenderer,
+  BarChart, PieChart, MapChart, EffectScatterChart, LineChart, PictorialBarChart, GraphChart, GaugeChart, ScatterChart,
+  GridComponent,
+  LegendComponent,
+  TooltipComponent,
+  TitleComponent,
+  DatasetComponent,
+  VisualMapComponent,
+  GeoComponent,
+  MarkPointComponent,
+  GraphicComponent,
+])
+
+export const registerEcharts = (app: any) => {
+  app.component('v-chart', ECharts)
+}

+ 55 - 17
src/utils/formatTime.ts

@@ -10,8 +10,8 @@
  * @returns {string} 返回拼接后的时间字符串
  */
 export function formatDate(date: any, format: string): string {
-	if(!date) return '';
-	date = new Date(date)
+	if (!date) return '';
+	date = new Date(date);
 	let we = date.getDay(); // 星期
 	let z = getWeek(date); // 周
 	let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
@@ -19,7 +19,7 @@ export function formatDate(date: any, format: string): string {
 		'Y+': date.getFullYear().toString(), // 年
 		'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 		'd+': date.getDate().toString(), // 日
-		'H+': date.getHours().toString(), // 时	
+		'H+': date.getHours().toString(), // 时
 		'M+': date.getMinutes().toString(), // 分
 		'S+': date.getSeconds().toString(), // 秒
 		'q+': qut, // 季度
@@ -93,33 +93,71 @@ export function formatAxis(param: Date): string {
 /**
  * @description 秒转换成xx:xx:xx格式
  * @param time 传入秒
+ * @param isMillisecond 是否是毫秒
  * @param showHour 是否展示小时 默认是
  * @description param 调用 `formatDuration(秒) 转换成xx:xx:xx的格式
  * @returns {string} 输出 即xx:xx:xx的格式;
  */
-export function formatDuration(time:any,showHour:boolean = true){
-	if(!time) return showHour ? '00:00:00' : '00:00';
-	if(time > -1){
+export function formatDuration(time: any, showHour: boolean = true, isMillisecond = false) {
+	if (!time) return showHour ? '00:00:00' : '00:00';
+	if (isMillisecond) {
+		time = Math.floor(time / 1000);
+	}
+	if (time > -1) {
 		const hour = Math.floor(time / 3600);
 		const min = Math.floor(time / 60) % 60;
 		const sec = time % 60;
-		if(showHour){ // 是否要展示小时
-			if(hour < 10) {
-				time = '0'+ hour + ":";
+		if (showHour) {
+			// 是否要展示小时
+			if (hour < 10) {
+				time = '0' + hour + ':';
 			} else {
-				time = hour + ":";
+				time = hour + ':';
 			}
-		}else{
+		} else {
 			time = '';
 		}
-		if(min < 10){
-			time += "0";
+		if (min < 10) {
+			time += '0';
 		}
-		time += min + ":";
-		if(sec < 10){
-			time += "0";
+		time += min + ':';
+		if (sec < 10) {
+			time += '0';
 		}
 		time += sec;
 	}
 	return time;
-}
+}
+/**
+ * @description 秒转换成xx天:xx小时:xx分钟xx秒格式
+ * @param time 传入秒
+ * @param isMillisecond 是否是毫秒
+ * @returns {string} 输出 即xx天:xx小时:xx分钟xx秒格式
+ */
+export function formatDurationDay(time: any, isMillisecond = false) {
+	if (!time) return '0秒';
+	if (isMillisecond) {
+		time = Math.floor(time / 1000);
+	}
+	if (time > -1) {
+		const day = Math.floor(time / 86400);
+		const hour = Math.floor((time % 86400) / 3600);
+		const min = Math.floor((time % 3600) / 60);
+		const sec = ( time % 60).toFixed(0);
+		let str = '';
+		if (day) {
+			str += day + '天';
+		}
+		if (hour) {
+			str += hour + '小时';
+		}
+		if (min) {
+			str += min + '分钟';
+		}
+		if (sec) {
+			str += sec + '秒';
+		}
+		return str;
+	}
+	return '0秒';
+}

+ 1 - 1
src/utils/ola_api.ts

@@ -45,7 +45,7 @@ export const ola: any = {
 	},
 
 	close: function () {
-		ola.ws.close();
+		ola.ws?.close();
 	},
 
 	_onOpen: function () {

+ 7 - 1
src/utils/request.ts

@@ -106,7 +106,12 @@ function httpErrorStatusHandle(error: any) {
 				if (!tokenAbnormal) {
 					tokenAbnormal = true;
 					// 弹出框
-					ElMessageBox.alert('登录已过期,请重新登录!', '提示', { type: 'warning', showClose: false, closeOnClickModal: false, draggable: true })
+					ElMessageBox.alert('登录已过期或该账户已在其他地方登录!', '提示', {
+						type: 'warning',
+						showClose: false,
+						closeOnClickModal: false,
+						draggable: true,
+					})
 						.then(() => {
 							Session.clear(); // 清除浏览器全部临时缓存
 							Local.clear(); // 清除浏览器全部临时缓存
@@ -170,6 +175,7 @@ function httpErrorStatusHandle(error: any) {
 		ElMessage({
 			type: 'error',
 			message,
+			grouping:true
 		});
 	}
 }

+ 58 - 19
src/utils/signalR.ts → src/utils/signalR.tsx

@@ -1,7 +1,53 @@
 // 官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/javascript-client?view=aspnetcore-6.0&viewFallbackFrom=aspnetcore-2.2&tabs=visual-studio
 import * as signalR from '@microsoft/signalr';
-import { Cookie, Local } from "@/utils/storage";
+import { Cookie, Local } from '@/utils/storage';
 import mittBus from '@/utils/mitt';
+import { ElButton, ElNotification } from 'element-plus';
+let notice: any = null;
+/**
+ * @description 判断是否需要更新
+ */
+async function judge() {
+	if (notice) notice.close();
+	// @ts-ignore'
+	const messageContent = () => {
+		return (
+			<div>
+				<p style="justify-content:flex-end">
+					<span class="mb10">检测到服务端发布了新版本,是否刷新页面?</span>
+					<div class="mt10">
+						<ElButton onClick={ignore} size="small">
+							忽略
+						</ElButton>
+						<ElButton type="primary" onClick={Refresh} size="small">
+							刷新
+						</ElButton>
+					</div>
+				</p>
+			</div>
+		);
+	};
+	notice = ElNotification({
+		title: '提示',
+		type: 'warning',
+		dangerouslyUseHTMLString: true,
+		message: messageContent(),
+		customClass: 'updateTips',
+		duration: 0,
+	});
+}
+/**
+ * @description 忽略
+ */
+const ignore = () => {
+	notice.close();
+};
+/**
+ * @description 刷新
+ */
+const Refresh = () => {
+	window.location.reload();
+};
 export default {
 	// signalR对象
 	SR: null as any,
@@ -23,9 +69,10 @@ export default {
 			// 建议用户重新刷新浏览器
 			await this.start();
 		});
-		connection.onreconnected(() => {
+		connection.onreconnected(async () => {
 			console.log('业务系统signal断线重连成功');
-			location.reload();
+			// location.reload();
+			await judge();
 			/*ElNotification({
 				type: 'success',
 				title: '提示',
@@ -38,32 +85,24 @@ export default {
 		});
 		// 服务端推送消息
 		connection.on('CircularRecord', (message: any) => {
-			mittBus.emit('CircularRecord', message);// 通知
+			mittBus.emit('CircularRecord', message); // 通知
 		});
 		// 服务端推送消息
 		connection.on('SeatState', (message: any) => {
-			mittBus.emit('SeatState', message);// 通知
+			mittBus.emit('SeatState', message); // 通知
 		});
 		// 服务端推送消息
 		connection.on('RestApplyPass', (message: any) => {
-			mittBus.emit('RestApplyPass', message);// 通知
+			mittBus.emit('RestApplyPass', message); // 通知
 		});
 		// 服务端推送消息
-		connection.on('BsSeatStateDataShowArea1', (message: any) => {
-
-		});
+		connection.on('BsSeatStateDataShowArea1', (message: any) => {});
 		// 服务端推送消息
-		connection.on('BsSeatStateDataShowArea2', (message: any) => {
-
-		});
+		connection.on('BsSeatStateDataShowArea2', (message: any) => {});
 		// 服务端推送消息
-		connection.on('BsSeatStateDataShowArea3', (message: any) => {
-
-		});
+		connection.on('BsSeatStateDataShowArea3', (message: any) => {});
 		// 服务端推送消息
-		connection.on('BsSeatStateDataShowArea4', (message: any) => {
-
-		});
+		connection.on('BsSeatStateDataShowArea4', (message: any) => {});
 	},
 	/**
 	 * @description 调用 this.signalR.start().then(async () => { await this.SR.invoke("method")})
@@ -182,5 +221,5 @@ export default {
 		} catch (error) {
 			console.log('signalR', error);
 		}
-	}
+	},
 };

+ 70 - 40
src/utils/tools.ts

@@ -1,4 +1,5 @@
-import axios from "axios";
+import axios from 'axios';
+
 /**
  * @description 防抖
  * @param func    功能函数(即要防抖的函数)
@@ -144,33 +145,62 @@ export function excludeSelfById(arr: Array<any>, id: string) {
 	});
 }
 /**
- * @description 下载文件
+ * @description 下载文件流
+ * @param res 文件流
+ * @param filename 文件名
+ */
+export function downloadFileByStream(res: Blob, filename: string) {
+	// 创建blob对象,解析流数据
+	const blob = new Blob([res], {
+		// 设置返回的文件类型
+		// type: 'application/pdf;charset=UTF-8' 表示下载文档为pdf,如果是word则设置为msword,excel为excel
+		type: res.type,
+	});
+	// 这里就是创建一个a标签,等下用来模拟点击事件
+	const a = document.createElement('a');
+	// 兼容webkix浏览器,处理webkit浏览器中href自动添加blob前缀,默认在浏览器打开而不是下载
+	const URL = window.URL || window.webkitURL;
+	// 根据解析后的blob对象创建URL 对象
+	// 下载链接
+	a.href = URL.createObjectURL(blob);
+	// 下载文件名,如果后端没有返回,可以自己写a.download = '文件.pdf'
+	a.download = filename;
+	document.body.appendChild(a);
+	// 点击a标签,进行下载
+	a.click();
+	// 收尾工作,在内存中移除URL 对象
+	document.body.removeChild(a);
+}
+/**
+ * @description 根据地址下载文件
  * @param src 文件地址
  * @param filename 文件名
  */
-export function downloadFile(src: string, filename: string) {
-	if(!src) {
-		return
+export function downloadFileBySrc(src: string, filename: string) {
+	if (!src) {
+		return;
 	}
-	let fileName: string = filename || '' // 文件名
+	let fileName: string = filename || ''; // 文件名
 	axios({
 		method: 'get',
 		url: src,
 		responseType: 'blob',
 		headers: { 'content-type': 'audio/mpeg' },
-	}).then((res: any) => {
-		let blob:Blob = new Blob([res.data], { type: res.data.type }) // 创建blob 设置blob文件类型 data 设置为后端返回的文件(例如mp3,jpeg) type:这里设置后端返回的类型 为 mp3
-		let down: HTMLAnchorElement = document.createElement('a') // 创建A标签
-		let href:string = window.URL.createObjectURL(blob) // 创建下载的链接
-		down.href = href // 下载地址
-		down.download = fileName // 下载文件名
-		document.body.appendChild(down)
-		down.click() // 模拟点击A标签
-		document.body.removeChild(down) // 下载完成移除元素
-		window.URL.revokeObjectURL(href) // 释放blob对象
-	}).catch(function (error) {
-		console.log(error)
 	})
+		.then((res: any) => {
+			let blob: Blob = new Blob([res.data], { type: res.data.type }); // 创建blob 设置blob文件类型 data 设置为后端返回的文件(例如mp3,jpeg) type:这里设置后端返回的类型 为 mp3
+			let down: HTMLAnchorElement = document.createElement('a'); // 创建A标签
+			let href: string = window.URL.createObjectURL(blob); // 创建下载的链接
+			down.href = href; // 下载地址
+			down.download = fileName; // 下载文件名
+			document.body.appendChild(down);
+			down.click(); // 模拟点击A标签
+			document.body.removeChild(down); // 下载完成移除元素
+			window.URL.revokeObjectURL(href); // 释放blob对象
+		})
+		.catch(function (error) {
+			console.log(error);
+		});
 }
 /**
  * @description 把后端返回的文件转换为前端需要的文件数据格式
@@ -202,8 +232,8 @@ export function transformFile(data: Array<any>[]) {
 	});
 }
 /**
-* @description 递归查找 callValue 对应的 enum 值
-* */
+ * @description 递归查找 callValue 对应的 enum 值
+ * */
 export function findItemNested(enumData: any, callValue: any, value: string, children: string) {
 	return enumData.reduce((accumulator: any, current: any) => {
 		if (accumulator) return accumulator;
@@ -212,25 +242,25 @@ export function findItemNested(enumData: any, callValue: any, value: string, chi
 	}, null);
 }
 /**
-* @description 根据枚举列表查询当需要的数据(如果指定了 label 和 value 的 key值,会自动识别格式化)
-* @param {String} callValue 当前单元格值
-* @param {Array} enumData 字典列表
-* @param {Array} fieldNames label && value && children 的 key 值
-* @param {String} type 过滤类型(目前只有 tag)
-* @returns {String}
-* */
-export function filterEnum(callValue: any, enumData?: any, fieldNames?: any, type?: "tag") {
-	const value = fieldNames?.value ?? "value";
-	const label = fieldNames?.label ?? "label";
-	const children = fieldNames?.children ?? "children";
+ * @description 根据枚举列表查询当需要的数据(如果指定了 label 和 value 的 key值,会自动识别格式化)
+ * @param {String} callValue 当前单元格值
+ * @param {Array} enumData 字典列表
+ * @param {Array} fieldNames label && value && children 的 key 值
+ * @param {String} type 过滤类型(目前只有 tag)
+ * @returns {String}
+ * */
+export function filterEnum(callValue: any, enumData?: any, fieldNames?: any, type?: 'tag') {
+	const value = fieldNames?.value ?? 'value';
+	const label = fieldNames?.label ?? 'label';
+	const children = fieldNames?.children ?? 'children';
 	let filterData: { [key: string]: any } = {};
 	// 判断 enumData 是否为数组
 	if (Array.isArray(enumData)) filterData = findItemNested(enumData, callValue, value, children);
 	// 判断是否输出的结果为 tag 类型
-	if (type == "tag") {
-		return filterData?.tagType ? filterData.tagType : "";
+	if (type == 'tag') {
+		return filterData?.tagType ? filterData.tagType : '';
 	} else {
-		return filterData ? filterData[label] : "--";
+		return filterData ? filterData[label] : '--';
 	}
 }
 
@@ -240,7 +270,7 @@ export function filterEnum(callValue: any, enumData?: any, fieldNames?: any, typ
  * @returns {String}
  * */
 export function handleProp(prop: string) {
-	const propArr = prop.split(".");
+	const propArr = prop.split('.');
 	if (propArr.length == 1) return prop;
 	return propArr[propArr.length - 1];
 }
@@ -257,8 +287,8 @@ export function isArray(val: any): val is Array<any> {
  * */
 export function formatValue(callValue: any) {
 	// 如果当前值为数组,使用 / 拼接(根据需求自定义)
-	if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : "";
-	return callValue ?? "";
+	if (isArray(callValue)) return callValue.length ? callValue.join(' / ') : '';
+	return callValue ?? '';
 }
 
 /**
@@ -268,7 +298,7 @@ export function formatValue(callValue: any) {
  * @returns {*}
  * */
 export function handleRowAccordingToProp(row: { [key: string]: any }, prop: string) {
-	if (!prop.includes(".")) return row[prop] ?? "";
-	prop.split(".").forEach(item => (row = row[item] ?? ""));
+	if (!prop.includes('.')) return row[prop] ?? '';
+	prop.split('.').forEach((item) => (row = row[item] ?? ''));
 	return row;
-}
+}

+ 9 - 10
src/views/auxiliary/advice/index.vue

@@ -3,17 +3,17 @@
 		<el-card shadow="never">
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
 				<el-form-item label="意见类型" prop="CommonType">
-					<el-select v-model="state.queryParams.CommonType" placeholder="请选择意见类型">
+					<el-select v-model="state.queryParams.CommonType" placeholder="请选择意见类型" @change="handleQuery">
 						<el-option v-for="item in commonType" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
 				<el-form-item label="常用语分类" prop="isOpen">
-					<el-select v-model="state.queryParams.isOpen" placeholder="请选择意见类型">
+					<el-select v-model="state.queryParams.isOpen" placeholder="请选择意见类型"  @change="handleQuery">
 						<el-option v-for="item in openState" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<el-button type="primary" @click="handleQuery" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
 					<el-button @click="resetQuery(ruleFormRef)" v-waves class="default-button"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
@@ -130,7 +130,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  state.queryParams.PageIndex = 1;
+  queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	publicCommonList(state.queryParams)
@@ -159,12 +164,6 @@ const adviceEditRef = ref<RefType>(); // 修改意见
 const updateAdvice = (row: any) => {
 	adviceEditRef.value.openDialog(row);
 };
-// 表格多选
-const multipleTableRef = ref<RefType>();
-const multipleSelection = ref<any>([]);
-const handleSelectionChange = (val: any[]) => {
-	multipleSelection.value = val;
-};
 // 删除参数
 const onAdviceDelete = () => {
 	const contents = proTableRef.value.selectedList.map((item: any) => item.content).join('、');

+ 10 - 5
src/views/auxiliary/businessTag/index.vue

@@ -3,20 +3,20 @@
 		<el-card shadow="never">
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
 				<el-form-item label="关键字" prop="Keyword">
-					<el-input v-model="state.queryParams.Keyword" placeholder="标签名称 " clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Keyword" placeholder="标签名称 " clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<el-form-item label="标签类型" prop="Type">
-					<el-select v-model="state.queryParams.Type" placeholder="请选择标签类型">
+					<el-select v-model="state.queryParams.Type" placeholder="请选择标签类型" @change="handleQuery">
 						<el-option v-for="item in tagType" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
 				<el-form-item label="业务类型" prop="BusinessType">
-					<el-select v-model="state.queryParams.BusinessType" placeholder="请选择业务类型">
+					<el-select v-model="state.queryParams.BusinessType" placeholder="请选择业务类型" @change="handleQuery">
 						<el-option v-for="item in businessTagType" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
@@ -118,7 +118,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	businessTagList(state.queryParams)

+ 1 - 1
src/views/business/citizen/components/Tags-edit.vue → src/views/auxiliary/citizen/components/Tags-edit.vue

@@ -110,7 +110,7 @@
 <script setup lang="ts" name="businessCitizenTagsEdit">
 import { reactive, ref } from 'vue';
 import { formatDate } from '@/utils/formatTime';
-import { citizenDetailByPhone, citizenLabelAdd, citizenLabelDelete } from '@/api/business/citizen';
+import { citizenDetailByPhone, citizenLabelAdd, citizenLabelDelete } from '@/api/auxiliary/citizen';
 import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { getImageUrl, throttle } from '@/utils/tools';
 

+ 109 - 0
src/views/auxiliary/citizen/components/Tags-record.vue

@@ -0,0 +1,109 @@
+<template>
+	<el-dialog v-model="state.dialogVisible" draggable title="查看标签记录" ref="dialogRef" width="60%" append-to-body>
+		<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
+			<el-form-item label="市民标签" prop="Label">
+				<el-input v-model="state.queryParams.Label" placeholder="市民标签" clearable @keyup.enter="handleQuery" />
+			</el-form-item>
+			<el-form-item label="记录人" prop="CreatorName">
+				<el-input v-model="state.queryParams.CreatorName" placeholder="记录人名称" clearable @keyup.enter="handleQuery" />
+			</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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
+			</el-form-item>
+		</el-form>
+		<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"
+			max-height="500px"
+		>
+		</ProTable>
+	</el-dialog>
+</template>
+
+<script setup lang="tsx" name="businessCitizenTagsRecord">
+import { reactive, ref } from 'vue';
+import type { FormInstance } from 'element-plus';
+import { citizenLabelList } from '@/api/auxiliary/citizen';
+import { formatDate } from '@/utils/formatTime';
+
+const proTableRef = ref<RefType>(); // 表格ref
+// 表格配置项
+const columns = ref<any[]>([
+	{ prop: 'label', label: '市民标签' },
+	{ prop: 'phone', label: '市民' },
+	{ prop: 'creatorName', label: '记录人' },
+	{
+		prop: 'creationTime',
+		label: '创建时间',
+		width: 170,
+		render: (scope: any) => {
+			return <span>{formatDate(scope.row.creationTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+]);
+// 定义变量内容
+const state = reactive({
+	dialogVisible: false, // 弹窗显示隐藏
+	queryParams: {
+		PageIndex: 1, // 当前页
+		PageSize: 10, // 每页条数
+		Keyword: null, // 关键字
+	},
+	tableData: [], // 表格数据
+	total: 0, // 总条数
+	loading: false, // 加载状态
+	citizen: {}, //市民信息
+});
+const ruleFormRef = ref<RefType>(); // 表单ref
+// 打开弹窗
+const openDialog = (row: any) => {
+	state.citizen = row;
+	queryList();
+	state.dialogVisible = true;
+};
+const dialogRef = ref<RefType>();
+// 关闭弹窗
+const closeDialog = () => {
+	state.dialogVisible = false;
+};
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+/** 重置按钮操作 */
+const resetQuery = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	handleQuery();
+};
+/** 获取历史工单 */
+const queryList = () => {
+	state.loading = true;
+	let request = {
+		...state.queryParams,
+		CitizenId: state.citizen.id,
+	};
+	citizenLabelList(request)
+		.then((response: any) => {
+			state.tableData = response?.result.items ?? [];
+			state.total = response?.result.total;
+			state.loading = false;
+		})
+		.catch(() => {
+			state.loading = false;
+		});
+};
+// 暴露变量
+defineExpose({
+	openDialog,
+	closeDialog,
+});
+</script>

+ 17 - 32
src/views/business/citizen/index.vue → src/views/auxiliary/citizen/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="business-citizen-container layout-pd">
+	<div class="auxiliary-citizen-container layout-pd">
 		<el-card shadow="never">
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
 				<el-form-item label="市民联系方式" prop="PhoneNumber">
@@ -7,22 +7,22 @@
 						v-model="state.queryParams.PhoneNumber"
 						placeholder="请输入市民联系方式"
 						clearable
-						@keyup.enter="queryList"
+						@keyup.enter="handleQuery"
 						class="keyword-input"
 					/>
 				</el-form-item>
 				<el-form-item label="市民标签" prop="Label">
-					<el-input v-model="state.queryParams.Label" placeholder="请输入市民标签" clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Label" placeholder="请输入市民标签" clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
 		</el-card>
 		<el-card shadow="never">
 			<!--      <div class="mb20">
-        <el-button type="primary" @click="onLexiconDelete" v-waves v-auth="'business:citizen:delete'" :disabled="!multipleSelection.length">
+        <el-button type="primary" @click="onLexiconDelete" v-waves v-auth="'auxiliary:citizen:delete'" :disabled="!multipleSelection.length">
           <SvgIcon name="ele-Delete" class="mr5" />删除
         </el-button>
       </div>-->
@@ -39,8 +39,8 @@
 			>
 				<!-- 表格操作 -->
 				<template #operation="{ row }">
-					<el-button link type="primary" @click="onTagsRecord(row)" v-auth="'business:citizen:tag'" title="查看市民标签记录"> 标签记录 </el-button>
-					<el-button link type="primary" @click="onTagsEdit(row)" v-auth="'business:citizen:edit'" title="编辑市民画像"> 编辑 </el-button>
+					<el-button link type="primary" @click="onTagsRecord(row)" v-auth="'auxiliary:citizen:tag'" title="查看市民标签记录"> 标签记录 </el-button>
+					<el-button link type="primary" @click="onTagsEdit(row)" v-auth="'auxiliary:citizen:edit'" title="编辑市民画像"> 编辑 </el-button>
 				</template>
 			</ProTable>
 		</el-card>
@@ -51,15 +51,15 @@
 	</div>
 </template>
 
-<script lang="tsx" setup name="businessCitizen">
+<script lang="tsx" setup name="auxiliaryCitizen">
 import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
 import { ElButton, ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { formatDate } from '@/utils/formatTime';
-import { citizenDelete, citizenList } from '@/api/business/citizen';
+import { citizenDelete, citizenList } from '@/api/auxiliary/citizen';
 
 // 引入组件
-const TagsRecord = defineAsyncComponent(() => import('@/views/business/citizen/components/Tags-record.vue')); // 标签记录
-const TagsEdit = defineAsyncComponent(() => import('@/views/business/citizen/components/Tags-edit.vue')); // 标签编辑
+const TagsRecord = defineAsyncComponent(() => import('@/views/auxiliary/citizen/components/Tags-record.vue')); // 标签记录
+const TagsEdit = defineAsyncComponent(() => import('@/views/auxiliary/citizen/components/Tags-edit.vue')); // 标签编辑
 
 const proTableRef = ref<RefType>(); // 表格ref
 // 表格配置项
@@ -101,7 +101,12 @@ const state = reactive({
 	tableData: [], // 表格数据
 });
 const ruleFormRef = ref<RefType>(null); // 表单ref
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	citizenList(state.queryParams)
@@ -130,26 +135,6 @@ const TagsEditRef = ref<RefType>();
 const onTagsEdit = (row: any) => {
 	TagsEditRef.value.openDialog(row);
 };
-// 删除参数
-const onLexiconDelete = () => {
-	const names = proTableRef.value.selectedList.map((item: any) => item.name).join('、');
-	const ids = proTableRef.value.selectedList.map((item: any) => item.id);
-	ElMessageBox.confirm(`您确定要删除:【${names}】市民画像,是否继续?`, '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-		draggable: true,
-		cancelButtonClass: 'default-button',
-		autofocus: false,
-	})
-		.then(() => {
-			citizenDelete({ ids }).then(() => {
-				ElMessage.success('操作成功');
-				queryList();
-			});
-		})
-		.catch(() => {});
-};
 // 页面加载时
 onMounted(() => {
 	queryList();

+ 10 - 5
src/views/auxiliary/knowledgeLexicon/index.vue

@@ -3,18 +3,18 @@
 		<el-card shadow="never">
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
 				<el-form-item label="关键词" prop="Tag">
-					<el-input v-model="state.queryParams.Tag" placeholder="请输入关键词" clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Tag" placeholder="请输入关键词" clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<el-form-item label="分类" prop="Classify">
-					<el-select v-model="state.queryParams.Classify" placeholder="请选择分类">
+					<el-select v-model="state.queryParams.Classify" placeholder="请选择分类" @change="handleQuery">
 						<el-option v-for="item in knowledgeWordClassify" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
 					</el-select>
 				</el-form-item>
 				<el-form-item label="近义词" prop="Synonym">
-					<el-input v-model="state.queryParams.Synonym" placeholder="请输入近义词" clearable @keyup.enter="queryList" />
+					<el-input v-model="state.queryParams.Synonym" placeholder="请输入近义词" clearable @keyup.enter="handleQuery" />
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
@@ -123,7 +123,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  state.queryParams.PageIndex = 1;
+  queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	knowledgeLexiconList(state.queryParams)

+ 9 - 3
src/views/auxiliary/message/index.vue

@@ -16,12 +16,12 @@
 					/>
 				</el-form-item>
 				<el-form-item label="短信类型" prop="PushBusiness">
-					<el-select v-model="state.queryParams.PushBusiness" placeholder="请选择短信类型">
+					<el-select v-model="state.queryParams.PushBusiness" placeholder="请选择短信类型" @change="handleQuery">
 						<el-option v-for="item in ePushBusinessData" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
 				<el-form-item label="发送状态" prop="Status">
-					<el-select v-model="state.queryParams.Status" placeholder="请选择发送状态">
+					<el-select v-model="state.queryParams.Status" placeholder="请选择发送状态" @change="handleQuery">
 						<el-option v-for="item in eSendStateData" :value="item.key" :key="item.key" :label="item.value" />
 					</el-select>
 				</el-form-item>
@@ -106,6 +106,7 @@ const handleTimeChange = (val: string[], startKey: string, endKey: string) => {
 		state.queryParams[startKey] = '';
 		state.queryParams[endKey] = '';
 	}
+	handleQuery();
 };
 // 受理时间
 const timeStartChangeCr = (val: string[]) => {
@@ -124,7 +125,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	const request = other.deepClone(state.queryParams);

+ 7 - 2
src/views/auxiliary/msgTemplate/index.vue

@@ -7,7 +7,7 @@
 						v-model="state.queryParams.Keyword"
 						placeholder="模板名称/模板内容/模板编码"
 						clearable
-						@keyup.enter="queryList"
+						@keyup.enter="handleQuery"
 						class="keyword-input"
 					/>
 				</el-form-item>
@@ -74,7 +74,12 @@ const state = reactive<any>({
 	tableData: [], // 表格数据
 });
 const ruleFormRef = ref<any>(null); // 表单ref
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  state.queryParams.PageIndex = 1;
+  queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	msgTemplateList(state.queryParams)

+ 0 - 4
src/views/auxiliary/notice/detail.vue

@@ -186,10 +186,6 @@ const closePage = () => {
 			path: '/auxiliary/notice',
 		});
 	} else if (noticeType.value === '公告详情') {
-		if (!router.hasRoute('auxiliaryNotice')) {
-			ElMessage.warning('未找到公告详情页面');
-			return;
-		}
 		router.push({
 			name: 'auxiliaryNotice',
 			state: {

+ 36 - 31
src/views/auxiliary/notice/index.vue

@@ -1,17 +1,17 @@
 <template>
 	<div class="auxiliary-notice-container layout-pd">
 		<el-card shadow="never">
-			<el-tabs v-model="listType" @tab-change="queryList">
+			<el-tabs v-model="listType" @tab-change="handleQuery">
 				<el-tab-pane name="0" label="通知"></el-tab-pane>
 				<el-tab-pane name="1" label="公告"></el-tab-pane>
 			</el-tabs>
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent class="mt10">
 				<el-form-item label="标题" prop="Title">
-					<el-input v-model="state.queryParams.Title" placeholder="标题" clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Title" placeholder="标题" clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<template v-if="listType === '0'">
 					<el-form-item label="通知类型" prop="CircularTypeId">
-						<el-select v-model="state.queryParams.CircularTypeId" placeholder="请选择通知类型">
+						<el-select v-model="state.queryParams.CircularTypeId" placeholder="请选择通知类型" @change="handleQuery">
 							<el-option v-for="item in circularType" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
 						</el-select>
 					</el-form-item>
@@ -31,7 +31,7 @@
 				</template>
 				<template v-else>
 					<el-form-item label="公告类型" prop="BulletinTypeId">
-						<el-select v-model="state.queryParams.BulletinTypeId" placeholder="请选择公告类型">
+						<el-select v-model="state.queryParams.BulletinTypeId" placeholder="请选择公告类型"  @change="handleQuery">
 							<el-option v-for="item in bulletinType" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
 						</el-select>
 					</el-form-item>
@@ -50,7 +50,7 @@
 					</el-form-item>
 				</template>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<el-button type="primary" @click="handleQuery" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
 					<el-button @click="resetQuery(ruleFormRef)" v-waves class="default-button"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
@@ -79,30 +79,30 @@
 					{{ row.isMustRead ? '是' : '否' }}
 				</template>
 				<template #readedNum="{ row }">
-          <el-popover placement="right" :width="450" trigger="click" popper-class="notice-container" v-if="[2].includes(row.circularState)">
-            <template #reference>
-              <el-button link type="primary">{{ row.readedNum + '/' + row.needReadNum }}</el-button>
-            </template>
-            <div class="notice-container-box">
-              <div class="notice-container-box-inner">
-                已读:{{ row.readedNum }}
-                <el-scrollbar class="mt5" v-if="row.circularReadGroups?.length">
-                  <el-tag v-for="item in row.circularReadGroups.filter((i) => i.isRead)"
-                  >{{ row.circularType === 1 ? item.userName : item.orgName }}
-                    <span v-if="item.isTimeOut" class="color-danger">(超时阅读)</span>
-                  </el-tag>
-                </el-scrollbar>
-              </div>
-              <div class="notice-container-box-inner">
-                未读:{{ row.needReadNum - row.readedNum }}
-                <el-scrollbar class="mt5 mb10" v-if="row.circularReadGroups?.length">
-                  <el-tag v-for="item in row.circularReadGroups.filter((i) => !i.isRead)">{{
-                      row.circularType === 1 ? item.userName : item.orgName
-                    }}</el-tag>
-                </el-scrollbar>
-              </div>
-            </div>
-          </el-popover>
+					<el-popover placement="right" :width="450" trigger="click" popper-class="notice-container" v-if="[2].includes(row.circularState)">
+						<template #reference>
+							<el-button link type="primary">{{ row.readedNum + '/' + row.needReadNum }}</el-button>
+						</template>
+						<div class="notice-container-box">
+							<div class="notice-container-box-inner">
+								已读:{{ row.readedNum }}
+								<el-scrollbar class="mt5" v-if="row.circularReadGroups?.length">
+									<el-tag v-for="item in row.circularReadGroups.filter((i) => i.isRead)"
+										>{{ row.circularType === 1 ? item.userName : item.orgName }}
+										<span v-if="item.isTimeOut" class="color-danger">(超时阅读)</span>
+									</el-tag>
+								</el-scrollbar>
+							</div>
+							<div class="notice-container-box-inner">
+								未读:{{ row.needReadNum - row.readedNum }}
+								<el-scrollbar class="mt5 mb10" v-if="row.circularReadGroups?.length">
+									<el-tag v-for="item in row.circularReadGroups.filter((i) => !i.isRead)">{{
+										row.circularType === 1 ? item.userName : item.orgName
+									}}</el-tag>
+								</el-scrollbar>
+							</div>
+						</div>
+					</el-popover>
 				</template>
 				<!-- 表格操作 -->
 				<template #operation="{ row }">
@@ -305,7 +305,7 @@ const handleTimeChange = (val: string[], startKey: string, endKey: string) => {
 		state.queryParams[startKey] = '';
 		state.queryParams[endKey] = '';
 	}
-	queryList();
+	handleQuery();
 };
 // 甄别时间
 const timeStartChangeCr = (val: string[]) => {
@@ -324,7 +324,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	switch (listType.value) {

+ 1 - 1
src/views/auxiliary/orderLexicon/components/Order-lexicon-add.vue

@@ -19,7 +19,7 @@
 						<el-switch v-model="state.ruleForm.isEnable" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="0" />
 					</el-form-item>
 				</el-col>
-				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="!state.ruleForm.classifyArray.includes('敏感标签')">
 					<el-form-item label="近义词" prop="synonym" :rules="[{ required: false, message: '请输入近义词', trigger: 'change' }]">
 						<el-tag v-for="tag in dynamicTags" :key="tag" class="mr10 mb10" size="large" closable @close="handleClose(tag)">
 							{{ tag }}

+ 1 - 1
src/views/auxiliary/orderLexicon/components/Order-lexicon-edit.vue

@@ -19,7 +19,7 @@
 						<el-switch v-model="state.ruleForm.isEnable" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="0" />
 					</el-form-item>
 				</el-col>
-				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" v-if="!state.ruleForm.classifyArray.includes('敏感标签')">
 					<el-form-item label="近义词" prop="synonym" :rules="[{ required: false, message: '请输入近义词', trigger: 'change' }]">
 						<el-tag
 							v-for="tag in dynamicTags"

+ 10 - 5
src/views/auxiliary/orderLexicon/index.vue

@@ -3,18 +3,18 @@
 		<el-card shadow="never">
 			<el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
 				<el-form-item label="关键词" prop="Tag">
-					<el-input v-model="state.queryParams.Tag" placeholder="请输入关键词" clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Tag" placeholder="请输入关键词" clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<el-form-item label="分类" prop="Classify">
-					<el-select v-model="state.queryParams.Classify" placeholder="请选择分类">
+					<el-select v-model="state.queryParams.Classify" placeholder="请选择分类" @change="handleQuery">
 						<el-option v-for="item in orderWordClassify" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
 					</el-select>
 				</el-form-item>
 				<el-form-item label="近义词" prop="Synonym">
-					<el-input v-model="state.queryParams.Synonym" placeholder="请输入近义词" clearable @keyup.enter="queryList" />
+					<el-input v-model="state.queryParams.Synonym" placeholder="请输入近义词" clearable @keyup.enter="handleQuery" />
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
 				</el-form-item>
 			</el-form>
@@ -122,7 +122,12 @@ const getBaseData = async () => {
 		console.log(error);
 	}
 };
-// 获取参数列表
+/** 搜索按钮操作 */
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+// 获取列表
 const queryList = () => {
 	state.loading = true;
 	orderLexiconList(state.queryParams)

+ 0 - 102
src/views/business/citizen/components/Tags-record.vue

@@ -1,102 +0,0 @@
-<template>
-  <el-dialog v-model="state.dialogVisible" draggable title="查看标签记录" ref="dialogRef" width="60%" append-to-body>
-    <el-form :model="state.queryParams" ref="ruleFormRef" inline @submit.native.prevent>
-      <el-form-item label="市民标签" prop="Label">
-        <el-input v-model="state.queryParams.Label" placeholder="市民标签" clearable @keyup.enter="handleQuery" />
-      </el-form-item>
-      <el-form-item label="记录人" prop="CreatorName">
-        <el-input v-model="state.queryParams.CreatorName" placeholder="记录人名称" clearable @keyup.enter="handleQuery" />
-      </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"> <SvgIcon name="ele-Refresh" class="mr5" />重置 </el-button>
-      </el-form-item>
-    </el-form>
-    <el-table :data="state.tableData" max-height="500px">
-      <el-table-column prop="label" label="市民标签" show-overflow-tooltip>
-      </el-table-column>
-      <el-table-column prop="phone" label="市民" show-overflow-tooltip> </el-table-column>
-      <el-table-column prop="creatorName" label="记录人" show-overflow-tooltip> </el-table-column>
-      <el-table-column prop="creationTime" label="创建时间" show-overflow-tooltip width="170">
-        <template #default="{ row }">
-          <span>{{ formatDate(row.creationTime, 'YYYY-mm-dd HH:MM:SS') }}</span>
-        </template>
-      </el-table-column>
-      <template #empty>
-        <Empty />
-      </template>
-    </el-table>
-    <pagination
-        :total="state.total"
-        v-model:page="state.queryParams.PageIndex"
-        v-model:limit="state.queryParams.PageSize"
-        @pagination="queryList"
-    />
-  </el-dialog>
-</template>
-
-<script setup lang="ts" name="businessCitizenTagsRecord">
-import {reactive, ref} from 'vue';
-import type {FormInstance} from 'element-plus';
-import {citizenLabelList} from "@/api/business/citizen"
-import {formatDate} from "@/utils/formatTime";
-// 定义变量内容
-const state = reactive<any>({
-  dialogVisible: false, // 弹窗显示隐藏
-  queryParams: {
-    PageIndex: 1, // 当前页
-    PageSize: 10, // 每页条数
-    Keyword: null,  // 关键字
-  },
-  tableData: [], // 表格数据
-  total: 0,   // 总条数
-  loading: false, // 加载状态
-  citizen:<EmptyObjectType>{},//市民信息
-});
-const ruleFormRef = ref<RefType>(); // 表单ref
-// 打开弹窗
-const openDialog = (row: any) => {
-  state.citizen = row;
-  queryList();
-  state.dialogVisible = true;
-};
-const dialogRef = ref<RefType>();
-// 关闭弹窗
-const closeDialog = () => {
-  state.dialogVisible = false;
-};
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  state.queryParams.PageIndex = 1;
-  queryList();
-};
-/** 重置按钮操作 */
-const resetQuery = (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  formEl.resetFields();
-  handleQuery();
-};
-/** 获取历史工单 */
-const queryList = () => {
-  state.loading = true;
-  let request = {
-    ...state.queryParams,
-    CitizenId: state.citizen.id
-  };
-  citizenLabelList(request)
-      .then((response: any) => {
-        state.tableData = response?.result.items ?? [];
-        state.total = response?.result.total;
-        state.loading = false;
-      })
-      .catch(() => {
-        state.loading = false;
-      });
-};
-// 暴露变量
-defineExpose({
-  openDialog,
-  closeDialog,
-});
-</script>
-

+ 310 - 0
src/views/business/countersign/index.vue

@@ -0,0 +1,310 @@
+<template>
+	<div class="business-countersign-container layout-pd">
+		<!-- 搜索  -->
+		<el-card shadow="never">
+			<div class="flex-center-align mb20">
+				<span style="color: var(--el-text-color-regular); display: inline-block; text-align: right; padding-right: 12px">快捷查询</span>
+				<el-radio-group v-model="fastSearch" @change="fastSearchChange">
+					<el-radio-button label="InitiatedCountersignature">发起的会签</el-radio-button>
+					<el-radio-button label="HandleCountersignature">已办会签</el-radio-button>
+				</el-radio-group>
+			</div>
+			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent label-width="100px">
+				<el-row :gutter="10">
+					<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="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="AcceptTypes">
+							<el-select
+								v-model="state.queryParams.AcceptTypes"
+								placeholder="请选择受理类型"
+								multiple
+								clearable
+								class="w100"
+								collapse-tags
+								collapse-tags-tooltip
+								:max-collapse-tags="2"
+							>
+								<el-option v-for="item in state.acceptTypeOptions" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
+							</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="Channels">
+								<el-select
+									v-model="state.queryParams.Channels"
+									placeholder="请选择来源渠道"
+									multiple
+									clearable
+									class="w100"
+									collapse-tags
+									collapse-tags-tooltip
+									:max-collapse-tags="2"
+								>
+									<el-option v-for="item in state.channelOptions" :value="item.dicDataValue" :key="item.dicDataValue" :label="item.dicDataName" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<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="CounterSignType">
+								<el-select v-model="state.queryParams.CounterSignType" placeholder="会签类型" class="w100" @change="handleQuery" clearable>
+									<el-option v-for="item in state.counterSignTypeOptions" :value="item.key" :key="item.key" :label="item.value" />
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</transition>
+					<transition name="el-zoom-in-top">
+						<el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6" v-show="!searchCol">
+							<el-form-item label="接办部门" prop="ActualHandleStepName">
+								<el-input v-model="state.queryParams.ActualHandleStepName" placeholder="接办部门名称" clearable @keyup.enter="handleQuery" />
+							</el-form-item>
+						</el-col>
+					</transition>
+					<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="HotspotIds">
+								<hot-spot-select
+									v-model="state.queryParams.HotspotIds"
+									class="w100"
+									:hotspotExternal="state.hotspotExternal"
+									show-checkbox
+									ref="hotSpotRef"
+									@confirm="handleQuery"
+								/>
+							</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 #expiredStatus="{ row }">
+					<span :class="'overdue-status-' + row.order?.expiredStatus" :title="row.order?.expiredStatusText"></span>
+				</template>
+				<template #isProvince="{ row }">
+					<span>{{ row.order?.isProvince ? '省工单' : '市工单' }}</span>
+				</template>
+				<template #title="{ row }">
+					<order-detail :order="row.order" @updateList="queryList">{{ row.order?.title }}</order-detail>
+				</template>
+				<!-- 表格操作 -->
+				<template #operation="{ row }">
+					<div class="flex-center-center">
+						<order-detail :order="row.order" @updateList="queryList" />
+						<el-button type="primary" link @click="onRecord(row)" class="ml10">流程明细</el-button>
+					</div>
+				</template>
+			</ProTable>
+		</el-card>
+
+		<!-- 流转记录 -->
+		<audit-record ref="auditRecordRef"></audit-record>
+	</div>
+</template>
+<script setup lang="tsx" name="businessCountersign">
+import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
+import { ElButton, FormInstance } from 'element-plus';
+import { formatDate } from '@/utils/formatTime';
+import { useRouter } from 'vue-router';
+import { countersignBase, countersignData } from "@/api/query/countersign";
+import { storeToRefs } from 'pinia';
+import { useUserInfo } from '@/stores/userInfo';
+// 引入组件
+const OrderDetail = defineAsyncComponent(() => import('@/components/OrderDetail/index.vue')); // 工单详情
+const AuditRecord = defineAsyncComponent(() => import('@/components/AuditRecord/index.vue')); // 流程明细
+const HotSpotSelect = defineAsyncComponent(() => import('@/components/Hotspot/index.vue')); // 选择热点
+
+const proTableRef = ref<RefType>(); // 表格ref
+// 表格配置项
+const columns = ref<any[]>([
+	{ prop: 'expiredStatus', label: '超期状态', align: 'center' },
+	{ prop: 'stateText', label: '会签状态', width: 120 },
+	{ prop: 'order.counterSignTypeText', label: '会签类型', width: 120 },
+	{ prop: 'order.no', label: '工单编码', width: 150 },
+	{ prop: 'order.sourceChannel', label: '来源方式', width: 100 },
+	{ prop: 'order.actualHandleStepName', label: '办理节点', width: 120 },
+	{
+		prop: 'order.startTime',
+		label: '受理时间',
+		width: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.order?.startTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'title', label: '工单标题', width: 300 },
+	{ prop: 'order.fromPhone', label: '来电号码', width: 150 },
+	{ prop: 'order.actualHandleOrgName', label: '接办部门', width: 150 },
+	{
+		prop: 'order.actualHandleTime',
+		label: '接办时间',
+		width: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.order?.actualHandleTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'order.hotspotName', label: '热点分类', width: 150 },
+	{ prop: 'starterOrgName', label: '会签发起部门', width: 150 },
+	{ prop: 'starterName', label: '会签发起人', width: 120 },
+	{
+		prop: 'creationTime',
+		label: '会签发起时间',
+		width: 170,
+		render: (scope) => {
+			return <span>{formatDate(scope.row.creationTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{
+		prop: 'order.expiredTime',
+		label: '工单期满时间',
+		width: 170,
+		render: (scope: any) => {
+			return <span>{formatDate(scope.row.order?.expiredTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+		},
+	},
+	{ prop: 'order.acceptType', label: '受理类型', width: 150 },
+	{ prop: 'order.hotspotName', label: '热点分类', width: 150 },
+	{ prop: 'operation', label: '操作', fixed: 'right', width: 180, align: 'center' },
+]);
+// 定义变量内容
+const ruleFormRef = ref<RefType>(); // 表单ref
+const router = useRouter(); // 路由
+const state = reactive<any>({
+	queryParams: {
+		// 查询条件
+		PageIndex: 1,
+		PageSize: 10,
+		Keyword: null, // 关键字
+		IsOnlyStarter: false, // 是否中心
+		HandleCountersignature: null, // 已办会签
+		InitiatedCountersignature: true, // 是否发起会签
+	},
+	tableData: [], //表单
+	loading: false, // 加载
+	total: 0, // 总数
+  acceptTypeOptions:[],// 受理类型
+  channelOptions:[],// 来源方式
+  counterSignTypeOptions:[],// 会签类型
+});
+const fastSearch = ref('InitiatedCountersignature');
+const fastSearchChange = (val: any) => {
+	fastSearch.value = val;
+	state.queryParams.HandleCountersignature = null;
+	state.queryParams.InitiatedCountersignature = null;
+	switch (val) {
+		case 'InitiatedCountersignature':
+			state.queryParams.InitiatedCountersignature = true;
+			break;
+		case 'HandleCountersignature':
+			state.queryParams.HandleCountersignature = true;
+			break;
+	}
+	handleQuery();
+};
+const storesUserInfo = useUserInfo();
+const { userInfos } = storeToRefs(storesUserInfo); // 用户信息
+const searchCol = ref(true); // 展开/收起
+// 展开/收起
+const closeSearch = () => {
+	searchCol.value = !searchCol.value;
+};
+// 手动查询,将页码设置为1
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
+/** 获取列表 */
+const queryList = () => {
+	state.loading = true;
+	state.queryParams.IsOnlyStarter = userInfos.value.isCenter;
+	countersignData(state.queryParams)
+		.then((res) => {
+			state.tableData = res.result?.items ?? [];
+			state.total = res.result?.total ?? 0;
+		})
+		.catch(() => {})
+		.finally(() => {
+			state.loading = false;
+		});
+};
+/** 重置按钮操作 */
+const hotSpotRef = ref<RefType>();
+const resetQuery = (formEl: FormInstance | undefined) => {
+	if (!formEl) return;
+	formEl.resetFields();
+	fastSearch.value = 'InitiatedCountersignature';
+	state.queryParams.HandleCountersignature = null;
+	state.queryParams.InitiatedCountersignature = true;
+	hotSpotRef.value?.reset();
+	queryList();
+};
+// 流转记录
+const auditRecordRef = ref<RefType>(); // 流转记录
+const onRecord = (row) => {
+	const params = {
+		dialogTitle: '流转记录',
+		...row.order,
+	};
+	auditRecordRef.value.openDialog(params);
+};
+// 查询基础信息
+const baseInfo = async () => {
+  try {
+    const {result} = await countersignBase();
+    state.acceptTypeOptions = result.acceptTypeOptions;
+    state.channelOptions = result.channelOptions;
+    state.counterSignTypeOptions = result.counterSignType;
+  }catch (e){
+    console.log(e)
+  }
+}
+onMounted(() => {
+  baseInfo();
+	queryList();
+});
+</script>
+<style scoped lang="scss">
+.business-countersign-container {
+	.arrow {
+		transition: transform var(--el-transition-duration);
+		cursor: pointer;
+	}
+	.arrow.is-reverse {
+		transform: rotateZ(-180deg);
+	}
+}
+</style>

+ 20 - 24
src/views/todo/delay/index.vue → src/views/business/delay/audit.vue

@@ -1,24 +1,22 @@
 <template>
-	<div class="todo-delay-container layout-pd">
+	<div class="business-delay-audit-container layout-pd">
 		<!-- 搜索  -->
 		<el-card shadow="never">
-			<el-tabs v-model="state.queryParams.IsApply" class="demo-tabs" @tab-change="handleClick">
+			<el-tabs v-model="state.queryParams.IsApply" @tab-change="handleClick">
 				<el-tab-pane name="false" label="延期待办"></el-tab-pane>
 				<el-tab-pane name="true" label="延期已办"></el-tab-pane>
 			</el-tabs>
 			<el-form :model="state.queryParams" ref="ruleFormRef" @submit.native.prevent inline>
 				<el-form-item label="关键字" prop="Keyword">
-					<el-input v-model="state.queryParams.Keyword" placeholder="工单编码/标题" clearable @keyup.enter="queryList" class="keyword-input" />
+					<el-input v-model="state.queryParams.Keyword" placeholder="工单编码/标题" clearable @keyup.enter="handleQuery" class="keyword-input" />
 				</el-form-item>
 				<el-form-item>
-					<el-button type="primary" @click="queryList" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
+					<el-button type="primary" @click="handleQuery" :loading="state.loading"> <SvgIcon name="ele-Search" class="mr5" />查询 </el-button>
 					<el-button @click="resetQuery(ruleFormRef)" v-waves class="default-button" :loading="state.loading">
 						<SvgIcon name="ele-Refresh" class="mr5" />重置
 					</el-button>
 				</el-form-item>
 			</el-form>
-			<!-- 表格 -->
-
 			<!-- 表格 -->
 			<ProTable
 				ref="proTableRef"
@@ -38,7 +36,7 @@
 					<span>{{ row.order?.isProvince ? '省工单' : '市工单' }}</span>
 				</template>
 				<template #title="{ row }">
-					<order-detail :order="row" @updateList="queryList">{{ row.order?.title }}</order-detail>
+					<order-detail :order="row.order" @updateList="queryList">{{ row.order?.title }}</order-detail>
 				</template>
 				<template #employeeName="{ row }">
 					<span
@@ -47,7 +45,7 @@
 				</template>
 				<!-- 表格操作 -->
 				<template #operation="{ row }">
-					<el-button link type="primary" @click="onDetail(row)" title="延期详情" v-auth="'todo:delay:audit'"> 延期详情 </el-button>
+					<el-button link type="primary" @click="onDetail(row)" title="延期详情"> 延期详情 </el-button>
 					<order-detail :order="row.order" @updateList="queryList" />
 				</template>
 			</ProTable>
@@ -58,10 +56,9 @@
 		<delay-edit ref="delayEditRef" @updateList="queryList" />
 	</div>
 </template>
-<script setup lang="tsx" name="todoDelay">
+<script setup lang="tsx" name="businessDelayAudit">
 import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
 import { ElButton, FormInstance } from 'element-plus';
-import { throttle } from '@/utils/tools';
 import { formatDate } from '@/utils/formatTime';
 import { useRouter } from 'vue-router';
 import { delayList } from '@/api/todo/delay';
@@ -107,7 +104,7 @@ const columns = ref<any[]>([
 	{ prop: 'order.hotspotName', label: '热点分类', width: 200 },
 	{ prop: 'order.acceptType', label: '受理类型', width: 150 },
 	{ prop: 'order.orgLevelOneName', label: '一级部门', width: 150 },
-	{ prop: 'order.actualHandleOrgName', label: '接办对象', width: 150 },
+	{ prop: 'order.actualHandleOrgName', label: '接办部门', width: 150 },
 	{
 		prop: 'order.actualHandleTime',
 		label: '接办时间',
@@ -130,11 +127,11 @@ const columns = ref<any[]>([
 	{ prop: 'delayUnitText', label: '延期申请单位', width: 120 },
 	{ prop: 'delayReason', label: '申请理由', width: 120 },
 	{
-		prop: 'applyDelayTime',
+		prop: 'beforeDelay',
 		label: '申请前期满时间',
 		width: 170,
 		render: (scope) => {
-			return <span>{formatDate(scope.row.applyDelayTime, 'YYYY-mm-dd HH:MM:SS')}</span>;
+			return <span>{formatDate(scope.row.beforeDelay, 'YYYY-mm-dd HH:MM:SS')}</span>;
 		},
 	},
 	{
@@ -147,8 +144,13 @@ const columns = ref<any[]>([
 	},
 	{ prop: 'operation', label: '操作', fixed: 'right', width: 160, align: 'center' },
 ]);
+// 手动查询,将页码设置为1
+const handleQuery = () => {
+	state.queryParams.PageIndex = 1;
+	queryList();
+};
 /** 获取列表 */
-const queryList = throttle(() => {
+const queryList = () => {
 	state.loading = true;
 	delayList(state.queryParams)
 		.then((res: any) => {
@@ -174,17 +176,17 @@ const queryList = throttle(() => {
 		.catch(() => {
 			state.loading = false;
 		});
-}, 300);
+};
 // 切换tab 查询列表
 const handleClick = () => {
-	queryList();
+	handleQuery();
 };
 /** 重置按钮操作 */
-const resetQuery = throttle((formEl: FormInstance | undefined) => {
+const resetQuery = (formEl: FormInstance | undefined) => {
 	if (!formEl) return;
 	formEl.resetFields();
 	queryList();
-}, 300);
+};
 // 延期详情
 const delayDetailRef = ref<RefType>();
 const delayEditRef = ref<RefType>();
@@ -201,12 +203,6 @@ const onDetail = async (row: any) => {
 		console.log(e);
 	}
 };
-// 表格多选
-const multipleTableRef = ref<RefType>();
-const multipleSelection = ref<any>([]);
-const handleSelectionChange = (val: any[]) => {
-	multipleSelection.value = val;
-};
 onMounted(() => {
 	queryList();
 });

+ 12 - 13
src/views/business/delay/components/Delay-detail.vue

@@ -43,11 +43,11 @@
 			<span class="dialog-footer">
 				<el-button @click="closeDialog" class="default-button">取 消</el-button>
 				<el-button type="primary" @click="processDetail">流程明细</el-button>
-				<el-button type="primary" @click="onAudit" v-if="state.ruleForm?.isCanHandle" v-auths="['todo:delay:audit', 'business:delay:audit']"
+				<el-button type="primary" @click="onAudit" v-if="state.ruleForm?.isCanHandle" v-auths="['business:delay:audit:todo', 'business:delay:audit']"
 					>延期审批</el-button
 				>
-<!--    6、隐藏【延期退回】功能    -->
-<!--				<el-button type="primary" @click="onReturn" v-if="state.ruleForm?.isCanHandle" v-auths="['todo:delay:return', 'business:delay:return']"
+				<!--    6、隐藏【延期退回】功能    -->
+				<!--				<el-button type="primary" @click="onReturn" v-if="state.ruleForm?.isCanHandle" v-auths="['todo:delay:return', 'business:delay:return']"
 					>延期退回</el-button
 				>-->
 			</span>
@@ -85,20 +85,19 @@ const state = reactive<any>({
 const openDialog = async (row: any) => {
 	state.loading = true;
 	state.currentId = row.id;
-  try {
-    const res = await delayDetail(state.currentId);
-    state.ruleForm = res.result ?? {};
-    state.ruleForm.files = transformFile(state.ruleForm.files);
-    state.loading = false;
-    state.dialogVisible = true;
-  } catch (e) {
-    state.loading = false;
-  }
+	try {
+		const res = await delayDetail(state.currentId);
+		state.ruleForm = res.result ?? {};
+		state.ruleForm.files = transformFile(state.ruleForm.files);
+		state.loading = false;
+		state.dialogVisible = true;
+	} catch (e) {
+		state.loading = false;
+	}
 };
 // 关闭弹窗
 const closeDialog = () => {
 	state.dialogVisible = false;
-	emit('updateList');
 };
 // 延期审批
 const processAuditRef = ref<RefType>(); // 流程审批ref

Some files were not shown because too many files changed in this diff