修改配色,修正呼叫状态下可以呼叫票号列表中的票号

main
cysamurai 3 months ago
parent 707e8c84f1
commit 36804fbeb9

@ -6,11 +6,14 @@ import { fileLogger, initFileLogger, writeFileLog, type FileLogLevel } from './f
import type { JsonValue } from '../shared/types/app-config' import type { JsonValue } from '../shared/types/app-config'
import { createLoginWindow, createMainWindow, createTicketWindow } from './window' import { createLoginWindow, createMainWindow, createTicketWindow } from './window'
type CallStatus = 'idle' | 'calling' | 'paused' | 'working' | 'evaluating' | 'transferring'
let sessionState: SessionState = { let sessionState: SessionState = {
empUid: null, empUid: null,
winUid: null, winUid: null,
queueToken: null, queueToken: null,
} }
let mainCallStatus: CallStatus = 'idle'
let loginWindow: BrowserWindow | null = null let loginWindow: BrowserWindow | null = null
let mainWindow: BrowserWindow | null = null let mainWindow: BrowserWindow | null = null
@ -169,6 +172,25 @@ ipcMain.handle('session:get', () => {
return sessionState 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 // ✅ 安全的 IPC 接口:设置 access token专用不暴露通用 setter
ipcMain.handle( ipcMain.handle(
'session:setAccessToken', 'session:setAccessToken',

@ -9,6 +9,8 @@ const api = {
onLoginSuccess: callback => ipcRenderer.on('login-success', callback), 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 let currentPauseCallback: ((event: Electron.IpcRendererEvent, reason: string) => void) | null = null
// const store = new Store() // const store = new Store()
@ -63,6 +65,11 @@ contextBridge.exposeInMainWorld('nativeDialog', {
ipcRenderer.invoke('dialog:confirm', options), ipcRenderer.invoke('dialog:confirm', options),
}) })
contextBridge.exposeInMainWorld('mainCallStatus', {
get: (): Promise<CallStatus> => ipcRenderer.invoke('mainCallStatus:get'),
set: (status: CallStatus): Promise<boolean> => ipcRenderer.invoke('mainCallStatus:set', status),
})
type MainTicketAction = 'call' | 'evaluate' type MainTicketAction = 'call' | 'evaluate'
type MainTicketActionPayload = { type MainTicketActionPayload = {
ticketUid?: number ticketUid?: number
@ -71,7 +78,8 @@ type MainTicketActionPayload = {
// TicketList -> Main.vue 触发call/evaluate // TicketList -> Main.vue 触发call/evaluate
contextBridge.exposeInMainWorld('ticketToMain', { 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) => triggerEvaluate: (payload?: MainTicketActionPayload) =>
ipcRenderer.send('ticket:main-action', 'evaluate', payload), ipcRenderer.send('ticket:main-action', 'evaluate', payload),
}) })

@ -1,5 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
import type { CallStatus } from './types/action'
import type { AppConfig, JsonValue } from '../../../shared/types/app-config' import type { AppConfig, JsonValue } from '../../../shared/types/app-config'
export {} export {}
@ -54,6 +55,11 @@ interface INativeDialog {
confirm: (options: INativeDialogConfirmOptions) => Promise<boolean> confirm: (options: INativeDialogConfirmOptions) => Promise<boolean>
} }
interface IMainCallStatus {
get: () => Promise<CallStatus>
set: (status: CallStatus) => Promise<boolean>
}
interface ITicketToMain { interface ITicketToMain {
triggerCall: (payload?: { ticketUid?: number; tktNum?: string }) => void triggerCall: (payload?: { ticketUid?: number; tktNum?: string }) => void
triggerEvaluate: (payload?: { ticketUid?: number; tktNum?: string }) => void triggerEvaluate: (payload?: { ticketUid?: number; tktNum?: string }) => void
@ -61,7 +67,10 @@ interface ITicketToMain {
interface IMainTicketEvents { interface IMainTicketEvents {
onAction: ( onAction: (
callback: (action: 'call' | 'evaluate', payload?: { ticketUid?: number; tktNum?: string }) => void, callback: (
action: 'call' | 'evaluate',
payload?: { ticketUid?: number; tktNum?: string },
) => void,
) => void ) => void
} }
@ -75,6 +84,7 @@ declare global {
appLogger: IAppLogger appLogger: IAppLogger
appConfig: IAppConfig appConfig: IAppConfig
nativeDialog: INativeDialog nativeDialog: INativeDialog
mainCallStatus: IMainCallStatus
ticketToMain: ITicketToMain ticketToMain: ITicketToMain
mainTicketEvents: IMainTicketEvents mainTicketEvents: IMainTicketEvents
} }

@ -6,6 +6,20 @@ import type { SessionState } from '../../../shared/types/session'
export const API_QUEUE_CALLER_PATH = '/api/queue/caller' export const API_QUEUE_CALLER_PATH = '/api/queue/caller'
const DEFAULT_API_PORT = 8845 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 * server_ip axios baseURL
* `192.168.1.10` 8845`192.168.1.10:8845``http://192.168.1.10:8845` * `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( instance.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
// 这里根据你的后端返回结构调整 // 这里根据你的后端返回结构调整
const { data } = response const data: HttpResponseEnvelope = response.data as HttpResponseEnvelope
// console.log('响应数据:', data) // console.log('响应数据:', data)
if (data.code !== 200) { if (data.code !== 200) {
ElMessage.error(data.message || '请求失败') const errMsg = getResponseMessage(data)
ElMessage.error(errMsg)
// token 过期处理 // token 过期处理
if (data.code === 401) { if (data.code === 401) {
ElMessage.error('请求token过期请重新登录') 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 => { error => {
// 处理 HTTP 状态码错误 // 处理 HTTP 状态码错误

@ -12,7 +12,7 @@ import {
import { SessionState } from 'src/shared/types/session' import { SessionState } from 'src/shared/types/session'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { api } from '../api' 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<void> => { const logErr = async (ctx: string, error: unknown): Promise<void> => {
const msg = error instanceof Error ? error.message : String(error) const msg = error instanceof Error ? error.message : String(error)
@ -42,7 +42,10 @@ const isActionSuccess = (res: unknown): boolean => {
} }
const getActionMessage = (res: unknown): string => { 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 => { const getActionTicketNo = (res: unknown): string => {
@ -64,7 +67,7 @@ const textColor = ref('#99ccff')
const iconColor = ref('#dcdfe6') const iconColor = ref('#dcdfe6')
const message = ref('欢迎使用紫云呼叫终端') const message = ref('欢迎使用紫云呼叫终端')
// :idle | :calling | :paused | :working | :evaluating | transferring // :idle | :calling | :paused | :working | :evaluating | transferring
const callStatus = ref('idle') const callStatus = ref<CallStatus>('idle')
const callBtnText = ref('呼叫') const callBtnText = ref('呼叫')
const pauseBtnText = ref('暂停') const pauseBtnText = ref('暂停')
const callingTkt = ref(-1) const callingTkt = ref(-1)
@ -198,6 +201,8 @@ function startQueueCountPolling(): void {
watch( watch(
callStatus, callStatus,
status => { status => {
void window.mainCallStatus.set(status)
if (status === 'evaluating') { if (status === 'evaluating') {
startIsRankPolling() startIsRankPolling()
} else { } else {
@ -333,11 +338,31 @@ const callAction = async (): Promise<void> => {
} else { } else {
// //
const m = getActionMessage(res) const m = getActionMessage(res)
updateLog(m) updateLog(m || '呼叫未成功')
await window.appLogger.log('warn', `呼叫未成功: ${m}`) await window.appLogger.log(
'warn',
`呼叫未成功: windowUid=${w}, empUid=${e}, ticketUid=${t}, response=${JSON.stringify(res)}`,
)
} }
} catch (error) { } catch (error) {
console.log('error', 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) await logErr('呼叫失败', error)
} }
} }
@ -568,6 +593,7 @@ const updateLog = (log: string): void => {
onMounted(async () => { onMounted(async () => {
sessionState.value = await window.session.get() sessionState.value = await window.session.get()
await window.mainCallStatus.set(callStatus.value)
// Login.vue appConfig selected_win_uid /getQueueCount // Login.vue appConfig selected_win_uid /getQueueCount
try { try {

@ -14,7 +14,12 @@
@change="handleStatusChange" @change="handleStatusChange"
> >
<el-option label="全部" :value="null" /> <el-option label="全部" :value="null" />
<el-option v-for="opt in statusSelectOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> <el-option
v-for="opt in statusSelectOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select> </el-select>
<el-button type="primary" @click="handleSearch"></el-button> <el-button type="primary" @click="handleSearch"></el-button>
</div> </div>
@ -98,7 +103,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ElConfigProvider } from 'element-plus' import { ElConfigProvider, ElMessageBox } from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { api } from '../api' import { api } from '../api'
@ -293,6 +298,19 @@ function normalizeRows(raw: unknown[]): Tkt[] {
} }
const handleCall = async (row: Tkt): Promise<void> => { const handleCall = async (row: Tkt): Promise<void> => {
const mainCallStatus = await window.mainCallStatus.get()
if (mainCallStatus !== 'idle') {
await ElMessageBox.alert('当前无法呼叫,请先结束当前正在呼叫或办理的票号。', '提示', {
type: 'warning',
confirmButtonText: '确定',
})
await window.appLogger.log(
'warn',
`票池呼叫被拦截: mainCallStatus=${mainCallStatus}, ticketUid=${row.ticketUid}, tktNum=${row.tktNum}`,
)
return
}
window.winControl.windowMinimize() window.winControl.windowMinimize()
window.ticketToMain.triggerCall({ window.ticketToMain.triggerCall({
ticketUid: row.ticketUid, ticketUid: row.ticketUid,

Loading…
Cancel
Save