From 36804fbeb943f735c759c976dae3ad9e3befcb30 Mon Sep 17 00:00:00 2001 From: cysamurai Date: Fri, 3 Apr 2026 19:00:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=85=8D=E8=89=B2=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=91=BC=E5=8F=AB=E7=8A=B6=E6=80=81=E4=B8=8B?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E5=91=BC=E5=8F=AB=E7=A5=A8=E5=8F=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=AD=E7=9A=84=E7=A5=A8=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- call-client/src/main/index.ts | 22 ++++++++++++ call-client/src/preload/index.ts | 10 +++++- call-client/src/renderer/src/env.d.ts | 12 ++++++- call-client/src/renderer/src/utils/service.ts | 23 +++++++++--- call-client/src/renderer/src/views/Main.vue | 36 ++++++++++++++++--- .../src/renderer/src/views/TicketList.vue | 22 ++++++++++-- 6 files changed, 112 insertions(+), 13 deletions(-) diff --git a/call-client/src/main/index.ts b/call-client/src/main/index.ts index 1de6260..77552bf 100644 --- a/call-client/src/main/index.ts +++ b/call-client/src/main/index.ts @@ -6,11 +6,14 @@ import { fileLogger, initFileLogger, writeFileLog, type FileLogLevel } from './f import type { JsonValue } from '../shared/types/app-config' import { createLoginWindow, createMainWindow, createTicketWindow } from './window' +type CallStatus = 'idle' | 'calling' | 'paused' | 'working' | 'evaluating' | 'transferring' + let sessionState: SessionState = { empUid: null, winUid: null, queueToken: null, } +let mainCallStatus: CallStatus = 'idle' let loginWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null @@ -169,6 +172,25 @@ ipcMain.handle('session:get', () => { return sessionState }) +ipcMain.handle('mainCallStatus:get', () => { + return mainCallStatus +}) + +ipcMain.handle('mainCallStatus:set', (_event: IpcMainInvokeEvent, status: unknown) => { + if ( + status !== 'idle' && + status !== 'calling' && + status !== 'paused' && + status !== 'working' && + status !== 'evaluating' && + status !== 'transferring' + ) { + throw new Error('Invalid main call status') + } + mainCallStatus = status + return true +}) + // ✅ 安全的 IPC 接口:设置 access token(专用,不暴露通用 setter) ipcMain.handle( 'session:setAccessToken', diff --git a/call-client/src/preload/index.ts b/call-client/src/preload/index.ts index 17949ff..fc9d450 100644 --- a/call-client/src/preload/index.ts +++ b/call-client/src/preload/index.ts @@ -9,6 +9,8 @@ const api = { onLoginSuccess: callback => ipcRenderer.on('login-success', callback), } +type CallStatus = 'idle' | 'calling' | 'paused' | 'working' | 'evaluating' | 'transferring' + let currentPauseCallback: ((event: Electron.IpcRendererEvent, reason: string) => void) | null = null // const store = new Store() @@ -63,6 +65,11 @@ contextBridge.exposeInMainWorld('nativeDialog', { ipcRenderer.invoke('dialog:confirm', options), }) +contextBridge.exposeInMainWorld('mainCallStatus', { + get: (): Promise => ipcRenderer.invoke('mainCallStatus:get'), + set: (status: CallStatus): Promise => ipcRenderer.invoke('mainCallStatus:set', status), +}) + type MainTicketAction = 'call' | 'evaluate' type MainTicketActionPayload = { ticketUid?: number @@ -71,7 +78,8 @@ type MainTicketActionPayload = { // TicketList -> Main.vue 触发(call/evaluate) contextBridge.exposeInMainWorld('ticketToMain', { - triggerCall: (payload?: MainTicketActionPayload) => ipcRenderer.send('ticket:main-action', 'call', payload), + triggerCall: (payload?: MainTicketActionPayload) => + ipcRenderer.send('ticket:main-action', 'call', payload), triggerEvaluate: (payload?: MainTicketActionPayload) => ipcRenderer.send('ticket:main-action', 'evaluate', payload), }) diff --git a/call-client/src/renderer/src/env.d.ts b/call-client/src/renderer/src/env.d.ts index 9f661bf..0711f38 100644 --- a/call-client/src/renderer/src/env.d.ts +++ b/call-client/src/renderer/src/env.d.ts @@ -1,5 +1,6 @@ /// +import type { CallStatus } from './types/action' import type { AppConfig, JsonValue } from '../../../shared/types/app-config' export {} @@ -54,6 +55,11 @@ interface INativeDialog { confirm: (options: INativeDialogConfirmOptions) => Promise } +interface IMainCallStatus { + get: () => Promise + set: (status: CallStatus) => Promise +} + interface ITicketToMain { triggerCall: (payload?: { ticketUid?: number; tktNum?: string }) => void triggerEvaluate: (payload?: { ticketUid?: number; tktNum?: string }) => void @@ -61,7 +67,10 @@ interface ITicketToMain { interface IMainTicketEvents { onAction: ( - callback: (action: 'call' | 'evaluate', payload?: { ticketUid?: number; tktNum?: string }) => void, + callback: ( + action: 'call' | 'evaluate', + payload?: { ticketUid?: number; tktNum?: string }, + ) => void, ) => void } @@ -75,6 +84,7 @@ declare global { appLogger: IAppLogger appConfig: IAppConfig nativeDialog: INativeDialog + mainCallStatus: IMainCallStatus ticketToMain: ITicketToMain mainTicketEvents: IMainTicketEvents } diff --git a/call-client/src/renderer/src/utils/service.ts b/call-client/src/renderer/src/utils/service.ts index 376babf..1f03330 100644 --- a/call-client/src/renderer/src/utils/service.ts +++ b/call-client/src/renderer/src/utils/service.ts @@ -6,6 +6,20 @@ import type { SessionState } from '../../../shared/types/session' export const API_QUEUE_CALLER_PATH = '/api/queue/caller' const DEFAULT_API_PORT = 8845 +type HttpResponseEnvelope = { + code?: number + message?: string + msg?: string + data?: unknown +} + +function getResponseMessage(data: HttpResponseEnvelope | undefined): string { + if (!data || typeof data !== 'object') return '请求失败' + if (typeof data.message === 'string' && data.message.trim()) return data.message + if (typeof data.msg === 'string' && data.msg.trim()) return data.msg + return '请求失败' +} + /** * 根据配置中的 server_ip 生成 axios baseURL。 * 支持:`192.168.1.10`(自动加端口 8845)、`192.168.1.10:8845`、`http://192.168.1.10:8845` @@ -76,19 +90,20 @@ instance.interceptors.request.use( instance.interceptors.response.use( (response: AxiosResponse) => { // 这里根据你的后端返回结构调整 - const { data } = response + const data: HttpResponseEnvelope = response.data as HttpResponseEnvelope // console.log('响应数据:', data) if (data.code !== 200) { - ElMessage.error(data.message || '请求失败') + const errMsg = getResponseMessage(data) + ElMessage.error(errMsg) // token 过期处理 if (data.code === 401) { ElMessage.error('请求token过期,请重新登录') } - return Promise.reject(new Error(data.message || '请求失败')) + return Promise.reject(new Error(errMsg)) } - return data.data // 直接返回 data,避免嵌套 + return data.data as AxiosResponse['data'] // 直接返回 data,避免嵌套 }, error => { // 处理 HTTP 状态码错误 diff --git a/call-client/src/renderer/src/views/Main.vue b/call-client/src/renderer/src/views/Main.vue index 81589ea..ca0b431 100644 --- a/call-client/src/renderer/src/views/Main.vue +++ b/call-client/src/renderer/src/views/Main.vue @@ -12,7 +12,7 @@ import { import { SessionState } from 'src/shared/types/session' import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { api } from '../api' -import type { ActionButton } from '../types/action' +import type { ActionButton, CallStatus } from '../types/action' const logErr = async (ctx: string, error: unknown): Promise => { const msg = error instanceof Error ? error.message : String(error) @@ -42,7 +42,10 @@ const isActionSuccess = (res: unknown): boolean => { } const getActionMessage = (res: unknown): string => { - return String(getActionData(res).message ?? '') + const data = getActionData(res) as { message?: unknown; msg?: unknown } + if (typeof data.message === 'string') return data.message + if (typeof data.msg === 'string') return data.msg + return '' } const getActionTicketNo = (res: unknown): string => { @@ -64,7 +67,7 @@ const textColor = ref('#99ccff') const iconColor = ref('#dcdfe6') const message = ref('欢迎使用紫云呼叫终端') // 待机:idle | 呼叫中:calling | 暂停中:paused | 办理中:working | 评价中:evaluating | 呼叫转移中:transferring -const callStatus = ref('idle') +const callStatus = ref('idle') const callBtnText = ref('呼叫') const pauseBtnText = ref('暂停') const callingTkt = ref(-1) @@ -198,6 +201,8 @@ function startQueueCountPolling(): void { watch( callStatus, status => { + void window.mainCallStatus.set(status) + if (status === 'evaluating') { startIsRankPolling() } else { @@ -333,11 +338,31 @@ const callAction = async (): Promise => { } else { // 刷新日志 const m = getActionMessage(res) - updateLog(m) - await window.appLogger.log('warn', `呼叫未成功: ${m}`) + updateLog(m || '呼叫未成功') + await window.appLogger.log( + 'warn', + `呼叫未成功: windowUid=${w}, empUid=${e}, ticketUid=${t}, response=${JSON.stringify(res)}`, + ) } } catch (error) { console.log('error', error) + const w = sessionState.value.winUid || -1 + const e = sessionState.value.empUid || -1 + const t = callingTkt.value > 0 ? callingTkt.value : null + const detail = + error instanceof Error + ? error.message + : (() => { + try { + return JSON.stringify(error) + } catch { + return String(error) + } + })() + await window.appLogger.log( + 'error', + `呼叫失败: windowUid=${w}, empUid=${e}, ticketUid=${t}, detail=${detail}`, + ) await logErr('呼叫失败', error) } } @@ -568,6 +593,7 @@ const updateLog = (log: string): void => { onMounted(async () => { sessionState.value = await window.session.get() + await window.mainCallStatus.set(callStatus.value) // 从 Login.vue 缓存的 appConfig 中读取 selected_win_uid,作为 /getQueueCount 参数来源 try { diff --git a/call-client/src/renderer/src/views/TicketList.vue b/call-client/src/renderer/src/views/TicketList.vue index 90df3bf..d80214b 100644 --- a/call-client/src/renderer/src/views/TicketList.vue +++ b/call-client/src/renderer/src/views/TicketList.vue @@ -14,7 +14,12 @@ @change="handleStatusChange" > - + 查询 @@ -98,7 +103,7 @@