增加callclient功能

main
cysamurai 3 months ago
parent 991ce5dc0f
commit 987f755ae2

@ -0,0 +1,3 @@
# 开发模式Vite 将 /api 代理到此处(见 electron.vite.config.ts
# 修改后需重启 npm run dev
VITE_DEV_PROXY_TARGET=http://192.168.1.10:8845

@ -0,0 +1,11 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}

@ -1,16 +1,34 @@
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'electron-vite' import { defineConfig, loadEnv } from 'electron-vite'
import { resolve } from 'path' import { resolve } from 'path'
export default defineConfig({ /**
main: {}, * Vite localhost:5173 axios localhost:8845 CORS
preload: {}, * /api service.ts DEV 使 /api/queue/caller
renderer: { * .env.development VITE_DEV_PROXY_TARGET=http://host:8845
resolve: { */
alias: { export default defineConfig(({ mode }) => {
'@renderer': resolve('src/renderer/src'), const env = loadEnv(mode, process.cwd(), '')
const devProxyTarget = env.VITE_DEV_PROXY_TARGET || 'http://127.0.0.1:8845'
return {
main: {},
preload: {},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src'),
},
},
plugins: [vue()],
server: {
proxy: {
'/api': {
target: devProxyTarget,
changeOrigin: true,
},
},
}, },
}, },
plugins: [vue()], }
},
}) })

@ -80,7 +80,7 @@ app.whenReady().then(() => {
}) })
// TicketList -> Main.vue 触发:用于“在票号列表窗口点呼叫/评价后,把主界面对应按钮逻辑执行起来” // TicketList -> Main.vue 触发:用于“在票号列表窗口点呼叫/评价后,把主界面对应按钮逻辑执行起来”
ipcMain.on('ticket:main-action', (_event, action: unknown) => { ipcMain.on('ticket:main-action', (_event, action: unknown, payload: unknown) => {
if (!mainWindow) return if (!mainWindow) return
if (action !== 'call' && action !== 'evaluate') return if (action !== 'call' && action !== 'evaluate') return
@ -88,7 +88,7 @@ app.whenReady().then(() => {
try { try {
mainWindow.show() mainWindow.show()
mainWindow.focus() mainWindow.focus()
mainWindow.webContents.send('main:ticket-action', action) mainWindow.webContents.send('main:ticket-action', action, payload)
} catch { } catch {
// ignore // ignore
} }

@ -63,26 +63,37 @@ contextBridge.exposeInMainWorld('nativeDialog', {
ipcRenderer.invoke('dialog:confirm', options), ipcRenderer.invoke('dialog:confirm', options),
}) })
type MainTicketAction = 'call' | 'evaluate'
type MainTicketActionPayload = {
ticketUid?: number
tktNum?: string
}
// TicketList -> Main.vue 触发call/evaluate // TicketList -> Main.vue 触发call/evaluate
contextBridge.exposeInMainWorld('ticketToMain', { contextBridge.exposeInMainWorld('ticketToMain', {
triggerCall: () => ipcRenderer.send('ticket:main-action', 'call'), triggerCall: (payload?: MainTicketActionPayload) => ipcRenderer.send('ticket:main-action', 'call', payload),
triggerEvaluate: () => ipcRenderer.send('ticket:main-action', 'evaluate'), triggerEvaluate: (payload?: MainTicketActionPayload) =>
ipcRenderer.send('ticket:main-action', 'evaluate', payload),
}) })
let currentMainTicketListener: let currentMainTicketListener:
| ((event: Electron.IpcRendererEvent, action: 'call' | 'evaluate') => void) | ((
event: Electron.IpcRendererEvent,
action: MainTicketAction,
payload?: MainTicketActionPayload,
) => void)
| null = null | null = null
contextBridge.exposeInMainWorld('mainTicketEvents', { contextBridge.exposeInMainWorld('mainTicketEvents', {
onAction: (callback: (action: 'call' | 'evaluate') => void) => { onAction: (callback: (action: MainTicketAction, payload?: MainTicketActionPayload) => void) => {
if (currentMainTicketListener) { if (currentMainTicketListener) {
ipcRenderer.removeListener('main:ticket-action', currentMainTicketListener) ipcRenderer.removeListener('main:ticket-action', currentMainTicketListener)
} }
currentMainTicketListener = (event, action) => { currentMainTicketListener = (event, action, payload) => {
void event void event
if (action !== 'call' && action !== 'evaluate') return if (action !== 'call' && action !== 'evaluate') return
callback(action) callback(action, payload)
} }
ipcRenderer.on('main:ticket-action', currentMainTicketListener) ipcRenderer.on('main:ticket-action', currentMainTicketListener)

@ -4,9 +4,10 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Electron</title> <title>Electron</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<!-- connect-src 使用 http:/https: 方案源,允许用户配置的 server_ip任意 IP/域名与端口),避免固定白名单导致本机 127.0.0.1 等被拦截 -->
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://padapi.queuingsystem.cn http://192.168.1.10:8845;" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http: https: ws: wss:;"
/> />
</head> </head>

@ -1,4 +1,10 @@
import type { CallRequest, CallResponse, PauseRequest, ReCallRequest } from '@renderer/types/action' import type { CallRequest, CallResponse, PauseRequest, ReCallRequest } from '@renderer/types/action'
import type {
IsRankData,
IsRankRequest,
QueueCountData,
QueueCountRequest,
} from '@renderer/types/rank'
import type { TicketPoolRequest, TicketPoolResponse } from '@renderer/types/ticket' import type { TicketPoolRequest, TicketPoolResponse } from '@renderer/types/ticket'
import type { UserRequest, UserResponse } from '@renderer/types/user' import type { UserRequest, UserResponse } from '@renderer/types/user'
import type { WindowResponse } from '@renderer/types/window' import type { WindowResponse } from '@renderer/types/window'
@ -17,6 +23,7 @@ export const api = {
action: { action: {
call: (data: CallRequest) => http.post<CallResponse>('/call-terminal/call', data), call: (data: CallRequest) => http.post<CallResponse>('/call-terminal/call', data),
init: (data: CallRequest) => http.post<CallResponse>('/call-terminal/init', data),
recall: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/recall', data), recall: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/recall', data),
abandon: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/abandon', data), abandon: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/abandon', data),
pause: (data: PauseRequest) => http.post<CallResponse>('/call-terminal/pause', data), pause: (data: PauseRequest) => http.post<CallResponse>('/call-terminal/pause', data),
@ -26,5 +33,12 @@ export const api = {
evaluate: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/evaluate', data), evaluate: (data: ReCallRequest) => http.post<CallResponse>('/call-terminal/evaluate', data),
pool: (params: TicketPoolRequest) => pool: (params: TicketPoolRequest) =>
http.get<TicketPoolResponse>('/call-terminal/pool', params), http.get<TicketPoolResponse>('/call-terminal/pool', params),
// evaluating 状态下轮询:判断当前票是否已完成评价/可进入 idle
isRank: (params: IsRankRequest) => http.get<IsRankData>('/call-terminal/is-rank', params),
// idle 状态下轮询:查询当前窗口等候人数
getQueueCount: (params: QueueCountRequest) =>
http.get<QueueCountData>('/call-terminal/queue-count', params),
}, },
} }

@ -55,12 +55,14 @@ interface INativeDialog {
} }
interface ITicketToMain { interface ITicketToMain {
triggerCall: () => void triggerCall: (payload?: { ticketUid?: number; tktNum?: string }) => void
triggerEvaluate: () => void triggerEvaluate: (payload?: { ticketUid?: number; tktNum?: string }) => void
} }
interface IMainTicketEvents { interface IMainTicketEvents {
onAction: (callback: (action: 'call' | 'evaluate') => void) => void onAction: (
callback: (action: 'call' | 'evaluate', payload?: { ticketUid?: number; tktNum?: string }) => void,
) => void
} }
declare global { declare global {

@ -10,6 +10,7 @@ export interface ActionButton {
export interface CallRequest { export interface CallRequest {
windowUid: number windowUid: number
empUid: number empUid: number
ticketUid?: number | null
} }
export interface CallResponse { export interface CallResponse {

@ -0,0 +1,48 @@
export interface IsRankData {
/**
* hasRank === true idle
*/
hasRank: boolean
/**
* UID
*/
ticketUid?: number
/**
* 使
*/
isEvaluated?: boolean
}
export interface IsRankRequest {
/**
* ticketUid Main.vue callingTkt
*/
ticketUid: number
}
export interface QueueCountData {
/**
*
*/
queueCount: number
/**
* windowUid
*/
windowUid?: number
/**
* count
*/
count?: number
}
export interface QueueCountRequest {
/**
* UID Login.vue windowUid
*/
windowUid: number
}

@ -36,6 +36,11 @@ const instance = axios.create({
}) })
export function applyServerIpToHttp(serverIp: string): void { export function applyServerIpToHttp(serverIp: string): void {
// DEV走当前页面源下的 /api/...,由 Vite 代理到后端,浏览器不跨域,不依赖后端 CORS
if (import.meta.env.DEV) {
instance.defaults.baseURL = API_QUEUE_CALLER_PATH
return
}
const url = buildBaseUrlFromServerIp(serverIp) const url = buildBaseUrlFromServerIp(serverIp)
instance.defaults.baseURL = url instance.defaults.baseURL = url
} }

@ -93,13 +93,25 @@ const handleLogin = async (): Promise<void> => {
value: win.windowUid.toString(), value: win.windowUid.toString(),
} }
}) })
if ( if (cachedWinKey.value && options.value.some(item => item.value === cachedWinKey.value)) {
cachedWinKey.value &&
options.value.some(item => item.value === cachedWinKey.value)
) {
selectedWin.value = cachedWinKey.value selectedWin.value = cachedWinKey.value
} }
const initWindowUid =
selectedWin.value.trim() !== ''
? parseInt(selectedWin.value)
: Number(sessionState.winUid ?? 0)
const initRes = await api.action.init({
empUid: Number(sessionState.empUid ?? -1),
windowUid: Number.isFinite(initWindowUid) ? initWindowUid : 0,
})
const initSuccess =
((initRes as { data?: { success?: boolean } })?.data?.success ??
(initRes as { success?: boolean })?.success) === true
if (!initSuccess) {
await window.appLogger.log('warn', '初始化接口调用完成,但返回 success=false')
}
// //
isLoginSuccessed.value = true isLoginSuccessed.value = true
isLoading.value = false isLoading.value = false
@ -351,7 +363,7 @@ onUnmounted(() => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
// justify-content: center; // justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #004d99 0%, #003b7a 100%);
overflow: hidden; overflow: hidden;
position: relative; position: relative;
padding: 0px; padding: 0px;
@ -484,14 +496,14 @@ onUnmounted(() => {
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
&:hover { &:hover {
color: #667eea; color: #004d99;
} }
} }
&.is-active { &.is-active {
.el-radio-button__inner { .el-radio-button__inner {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #004d99 0%, #003b7a 100%);
border-color: #667eea; border-color: #004d99;
box-shadow: none; box-shadow: none;
} }
} }
@ -517,11 +529,11 @@ onUnmounted(() => {
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover { &:hover {
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15); box-shadow: 0 4px 12px rgba(0, 77, 153, 0.15);
} }
&.is-focus { &.is-focus {
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25); box-shadow: 0 4px 12px rgba(0, 77, 153, 0.25);
} }
} }
} }
@ -534,14 +546,14 @@ onUnmounted(() => {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
border-radius: 8px; border-radius: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #004d99 0%, #003b7a 100%);
border: none; border: none;
transition: all 0.3s ease; transition: all 0.3s ease;
margin-top: 10px; margin-top: 10px;
&:hover:not(.is-disabled) { &:hover:not(.is-disabled) {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3); box-shadow: 0 8px 20px rgba(0, 77, 153, 0.3);
} }
&:active:not(.is-disabled) { &:active:not(.is-disabled) {
@ -569,10 +581,6 @@ onUnmounted(() => {
// //
@media (max-width: 480px) { @media (max-width: 480px) {
.login-container {
// padding: 15px;
}
.form-wrapper { .form-wrapper {
padding: 25px 20px !important; padding: 25px 20px !important;
} }

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
CircleCheck, CircleCheck,
Delete,
Menu, Menu,
Phone, Phone,
Star, Star,
@ -9,7 +10,7 @@ import {
VideoPlay, VideoPlay,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { SessionState } from 'src/shared/types/session' import { SessionState } from 'src/shared/types/session'
import { computed, onMounted, onUnmounted, ref } 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 } from '../types/action'
@ -18,6 +19,41 @@ const logErr = async (ctx: string, error: unknown): Promise<void> => {
await window.appLogger.log('error', `${ctx}: ${msg}`) await window.appLogger.log('error', `${ctx}: ${msg}`)
} }
type ActionResultLike = {
success?: boolean
message?: string
ticketNo?: string
ticketUid?: number
data?: {
success?: boolean
message?: string
ticketNo?: string
ticketUid?: number
}
}
const getActionData = (res: unknown): NonNullable<ActionResultLike['data']> | ActionResultLike => {
const r = (res ?? {}) as ActionResultLike
return r.data && typeof r.data === 'object' ? r.data : r
}
const isActionSuccess = (res: unknown): boolean => {
return getActionData(res).success === true
}
const getActionMessage = (res: unknown): string => {
return String(getActionData(res).message ?? '')
}
const getActionTicketNo = (res: unknown): string => {
return String(getActionData(res).ticketNo ?? '')
}
const getActionTicketUid = (res: unknown): number => {
const v = getActionData(res).ticketUid
return typeof v === 'number' ? v : -1
}
const sessionState = ref<SessionState>({ const sessionState = ref<SessionState>({
empUid: -1, empUid: -1,
winUid: -1, winUid: -1,
@ -32,9 +68,31 @@ const callStatus = ref('idle')
const callBtnText = ref('呼叫') const callBtnText = ref('呼叫')
const pauseBtnText = ref('暂停') const pauseBtnText = ref('暂停')
const callingTkt = ref(-1) const callingTkt = ref(-1)
// Login.vue UID /getQueueCount
const cachedWindowUid = ref(-1)
const EVALUATING_COUNTDOWN_SEC = 15 const EVALUATING_COUNTDOWN_SEC = 15
let evaluatingCountdownTimer: ReturnType<typeof setInterval> | null = null let evaluatingCountdownTimer: ReturnType<typeof setInterval> | null = null
const evaluatingPrefixText = ref('评价中')
let isRankPollingTimer: ReturnType<typeof setInterval> | null = null
let isRankPollingBusy = false
let queueCountPollingTimer: ReturnType<typeof setInterval> | null = null
function clearIsRankPolling(): void {
if (isRankPollingTimer !== null) {
clearInterval(isRankPollingTimer)
isRankPollingTimer = null
}
}
function clearQueueCountPolling(): void {
if (queueCountPollingTimer !== null) {
clearInterval(queueCountPollingTimer)
queueCountPollingTimer = null
}
}
function clearEvaluatingCountdown(): void { function clearEvaluatingCountdown(): void {
if (evaluatingCountdownTimer !== null) { if (evaluatingCountdownTimer !== null) {
@ -49,6 +107,7 @@ function clearEvaluatingCountdown(): void {
*/ */
function startEvaluatingCountdown(prefixText: string): void { function startEvaluatingCountdown(prefixText: string): void {
clearEvaluatingCountdown() clearEvaluatingCountdown()
evaluatingPrefixText.value = prefixText
let left = EVALUATING_COUNTDOWN_SEC let left = EVALUATING_COUNTDOWN_SEC
const applyMessage = (): void => { const applyMessage = (): void => {
message.value = `${prefixText}(剩余 ${left} 秒)` message.value = `${prefixText}(剩余 ${left} 秒)`
@ -58,18 +117,102 @@ function startEvaluatingCountdown(prefixText: string): void {
evaluatingCountdownTimer = setInterval(() => { evaluatingCountdownTimer = setInterval(() => {
left -= 1 left -= 1
if (left <= 0) { if (left <= 0) {
clearEvaluatingCountdown()
// idle /isRank
message.value = evaluatingPrefixText.value
return
}
applyMessage()
}, 1000)
}
async function pollIsRankOnce(): Promise<void> {
if (isRankPollingBusy) return
isRankPollingBusy = true
try {
const ticketUid = callingTkt.value
if (typeof ticketUid !== 'number' || ticketUid <= 0) return
const res = await api.action.isRank({ ticketUid })
const ranked = res.hasRank === true || res.isEvaluated === true
if (ranked) {
clearIsRankPolling()
clearEvaluatingCountdown() clearEvaluatingCountdown()
callStatus.value = 'idle' callStatus.value = 'idle'
callBtnText.value = '呼叫' callBtnText.value = '呼叫'
callingTkt.value = -1 callingTkt.value = -1
message.value = '欢迎使用紫云呼叫终端' message.value = '欢迎使用紫云呼叫终端'
void window.appLogger.log('info', '评价倒计时结束,进入待机') await window.appLogger.log('info', 'isRank: 评价完成,进入待机')
return return
} }
applyMessage()
// message
if (evaluatingCountdownTimer === null) {
message.value = `${evaluatingPrefixText.value}(等待评价完成...`
}
} catch (error) {
await logErr('查询 isRank 失败', error)
} finally {
isRankPollingBusy = false
}
}
function startIsRankPolling(): void {
clearIsRankPolling()
void pollIsRankOnce()
isRankPollingTimer = setInterval(() => {
void pollIsRankOnce()
}, 1000) }, 1000)
} }
async function pollQueueCountOnce(): Promise<void> {
try {
const winUidFromCache = cachedWindowUid.value
const winUidFromSession = sessionState.value.winUid ?? -1
const windowUid = winUidFromCache > 0 ? winUidFromCache : winUidFromSession
if (typeof windowUid !== 'number' || windowUid <= 0) return
const res = await api.action.getQueueCount({ windowUid })
const count =
typeof res.queueCount === 'number'
? res.queueCount
: typeof res.count === 'number'
? res.count
: 0
message.value = `欢迎使用紫云呼叫终端,当前窗口等候人数:${count}`
} catch (error) {
await logErr('查询 getQueueCount 失败', error)
}
}
function startQueueCountPolling(): void {
clearQueueCountPolling()
void pollQueueCountOnce()
queueCountPollingTimer = setInterval(() => {
void pollQueueCountOnce()
}, 15000)
}
// callStatus
// evaluating /isRankidle 15 /getQueueCount
watch(
callStatus,
status => {
if (status === 'evaluating') {
startIsRankPolling()
} else {
clearIsRankPolling()
}
if (status === 'idle') {
startQueueCountPolling()
} else {
clearQueueCountPolling()
}
},
{ immediate: true },
)
// buttons computedworking // buttons computedworking
const buttons = computed(() => { const buttons = computed(() => {
const startOrComplete = const startOrComplete =
@ -99,6 +242,12 @@ const buttons = computed(() => {
), ),
}, },
startOrComplete, startOrComplete,
{
icon: Delete,
label: '弃号',
action: 'abandon',
enabled: callStatus.value === 'calling',
},
{ {
icon: Switch, icon: Switch,
label: '转移', label: '转移',
@ -144,8 +293,14 @@ const callAction = async (): Promise<void> => {
empUid: e, empUid: e,
ticketUid: t, ticketUid: t,
}) })
updateLog(`已重呼:${res.ticketNo},请勿重复点击!`) if (isActionSuccess(res)) {
await window.appLogger.log('info', `重呼成功: ticketNo=${res.ticketNo}`) updateLog(`已重呼:${getActionTicketNo(res)},请勿重复点击!`)
await window.appLogger.log('info', `重呼成功: ticketNo=${getActionTicketNo(res)}`)
} else {
const m = getActionMessage(res)
updateLog(m)
await window.appLogger.log('warn', `重呼未成功: ${m}`)
}
return return
} catch (error) { } catch (error) {
console.log('error', error) console.log('error', error)
@ -156,27 +311,30 @@ const callAction = async (): Promise<void> => {
try { try {
const w = sessionState.value.winUid || -1 const w = sessionState.value.winUid || -1
const e = sessionState.value.empUid || -1 const e = sessionState.value.empUid || -1
await window.appLogger.log('info', `请求呼叫: windowUid=${w}, empUid=${e}`) const t = callingTkt.value > 0 ? callingTkt.value : null
await window.appLogger.log('info', `请求呼叫: windowUid=${w}, empUid=${e}, ticketUid=${t}`)
const res = await api.action.call({ const res = await api.action.call({
windowUid: w, windowUid: w,
empUid: e, empUid: e,
ticketUid: t,
}) })
// //
if (res.success === true) { if (isActionSuccess(res)) {
console.log('call success') console.log('call success')
callStatus.value = 'calling' callStatus.value = 'calling'
callBtnText.value = '重呼' callBtnText.value = '重呼'
callingTkt.value = res.ticketUid callingTkt.value = getActionTicketUid(res)
updateLog(`正在呼叫:${res.ticketNo}`) updateLog(`正在呼叫:${getActionTicketNo(res)}`)
await window.appLogger.log( await window.appLogger.log(
'info', 'info',
`呼叫成功: ticketNo=${res.ticketNo}, ticketUid=${res.ticketUid}`, `呼叫成功: ticketNo=${getActionTicketNo(res)}, ticketUid=${getActionTicketUid(res)}`,
) )
} else { } else {
// //
updateLog(res.message) const m = getActionMessage(res)
await window.appLogger.log('warn', `呼叫未成功: ${res.message}`) updateLog(m)
await window.appLogger.log('warn', `呼叫未成功: ${m}`)
} }
} catch (error) { } catch (error) {
console.log('error', error) console.log('error', error)
@ -184,6 +342,36 @@ const callAction = async (): Promise<void> => {
} }
} }
// action
const abandonAction = async (): Promise<void> => {
try {
const w = sessionState.value.winUid || -1
const e = sessionState.value.empUid || -1
const t = callingTkt.value
await window.appLogger.log('info', `请求弃号: windowUid=${w}, empUid=${e}`)
const res = await api.action.abandon({
windowUid: w,
empUid: e,
ticketUid: t,
})
if (isActionSuccess(res)) {
callStatus.value = 'idle'
callBtnText.value = '呼叫'
callingTkt.value = -1
updateLog(`弃号成功: ${getActionTicketNo(res)}`)
await window.appLogger.log('info', `弃号成功: ticketNo=${getActionTicketNo(res)}`)
} else {
const m = getActionMessage(res) || 'unknown'
updateLog(`弃号未成功: ${m}`)
await window.appLogger.log('warn', `弃号未成功: ${m}`)
}
} catch (error) {
console.log('error', error)
await logErr('弃号失败', error)
}
}
// action // action
const startAction = async (): Promise<void> => { const startAction = async (): Promise<void> => {
try { try {
@ -196,12 +384,13 @@ const startAction = async (): Promise<void> => {
empUid: e, empUid: e,
ticketUid: t, ticketUid: t,
}) })
if (res.success) { if (isActionSuccess(res)) {
callStatus.value = 'working' callStatus.value = 'working'
updateLog(`正在办理:${res.ticketNo}`) updateLog(`正在办理:${getActionTicketNo(res)}`)
await window.appLogger.log('info', `开始办理成功: ticketNo=${res.ticketNo}`) await window.appLogger.log('info', `开始办理成功: ticketNo=${getActionTicketNo(res)}`)
} else { } else {
await window.appLogger.log('warn', `开始办理未成功: ${res.message ?? 'unknown'}`) const m = getActionMessage(res) || 'unknown'
await window.appLogger.log('warn', `开始办理未成功: ${m}`)
} }
} catch (error) { } catch (error) {
console.log('error', error) console.log('error', error)
@ -221,15 +410,16 @@ const completeAction = async (): Promise<void> => {
empUid: e, empUid: e,
ticketUid: t, ticketUid: t,
}) })
if (!res.success) { if (!isActionSuccess(res)) {
await window.appLogger.log('warn', `完成办理未成功: ${res.message ?? 'unknown'}`) const m = getActionMessage(res) || 'unknown'
await window.appLogger.log('warn', `完成办理未成功: ${m}`)
return return
} }
await window.appLogger.log('info', `完成办理成功: ticketNo=${res.ticketNo}`) await window.appLogger.log('info', `完成办理成功: ticketNo=${getActionTicketNo(res)}`)
callStatus.value = 'evaluating' callStatus.value = 'evaluating'
callBtnText.value = '呼叫' callBtnText.value = '呼叫'
startEvaluatingCountdown(`办理完成:${res.ticketNo},评价中`) startEvaluatingCountdown(`办理完成:${getActionTicketNo(res)},评价中`)
await window.appLogger.log('info', '完成办理成功,进入评价中状态') await window.appLogger.log('info', '完成办理成功,进入评价中状态')
} catch (error) { } catch (error) {
console.log('error', error) console.log('error', error)
@ -302,13 +492,14 @@ const pauseAction = async (): Promise<void> => {
empUid: e, empUid: e,
pauseReason: reason, pauseReason: reason,
}) })
if (res.success) { if (isActionSuccess(res)) {
callStatus.value = 'paused' callStatus.value = 'paused'
pauseBtnText.value = '恢复' pauseBtnText.value = '恢复'
updateLog(`暂停中,原因:${reason}`) updateLog(`暂停中,原因:${reason}`)
await window.appLogger.log('info', `暂停成功: reason=${reason}`) await window.appLogger.log('info', `暂停成功: reason=${reason}`)
} else { } else {
await window.appLogger.log('warn', `暂停未成功: ${res.message ?? 'unknown'}`) const m = getActionMessage(res) || 'unknown'
await window.appLogger.log('warn', `暂停未成功: ${m}`)
} }
} catch (err) { } catch (err) {
await logErr('暂停失败', err) await logErr('暂停失败', err)
@ -323,12 +514,13 @@ const pauseAction = async (): Promise<void> => {
windowUid: w, windowUid: w,
empUid: e, empUid: e,
}) })
if (res.success) { if (isActionSuccess(res)) {
callStatus.value = 'idle' callStatus.value = 'idle'
pauseBtnText.value = '暂停' pauseBtnText.value = '暂停'
await window.appLogger.log('info', '恢复成功') await window.appLogger.log('info', '恢复成功')
} else { } else {
await window.appLogger.log('warn', `恢复未成功: ${res.message ?? 'unknown'}`) const m = getActionMessage(res) || 'unknown'
await window.appLogger.log('warn', `恢复未成功: ${m}`)
} }
} catch (err) { } catch (err) {
await logErr('恢复失败', err) await logErr('恢复失败', err)
@ -345,6 +537,9 @@ const handleButtonClick = (button: ActionButton): void => {
case 'call': case 'call':
callAction() callAction()
break break
case 'abandon':
abandonAction()
break
case 'pause': case 'pause':
pauseAction() pauseAction()
break break
@ -374,8 +569,24 @@ const updateLog = (log: string): void => {
onMounted(async () => { onMounted(async () => {
sessionState.value = await window.session.get() sessionState.value = await window.session.get()
// Login.vue appConfig selected_win_uid /getQueueCount
try {
const cfg = await window.appConfig.getAll()
const v = (cfg as Record<string, unknown>)?.selected_win_uid
const n = typeof v === 'number' ? v : Number(v)
if (Number.isFinite(n)) {
cachedWindowUid.value = n
}
} catch (error) {
// 退使 sessionState.winUid
await logErr('读取 selected_win_uid 失败', error)
}
// TicketList -> Main.vue // TicketList -> Main.vue
window.mainTicketEvents.onAction(action => { window.mainTicketEvents.onAction((action, payload) => {
if (typeof payload?.ticketUid === 'number' && payload.ticketUid > 0) {
callingTkt.value = payload.ticketUid
}
if (action === 'call') { if (action === 'call') {
void callAction() void callAction()
return return
@ -389,6 +600,8 @@ onMounted(async () => {
onUnmounted(() => { onUnmounted(() => {
clearEvaluatingCountdown() clearEvaluatingCountdown()
clearIsRankPolling()
clearQueueCountPolling()
}) })
</script> </script>

@ -294,7 +294,10 @@ function normalizeRows(raw: unknown[]): Tkt[] {
const handleCall = async (row: Tkt): Promise<void> => { const handleCall = async (row: Tkt): Promise<void> => {
window.winControl.windowMinimize() window.winControl.windowMinimize()
window.ticketToMain.triggerCall() window.ticketToMain.triggerCall({
ticketUid: row.ticketUid,
tktNum: row.tktNum,
})
await window.appLogger.log( await window.appLogger.log(
'info', 'info',
`票池呼叫按钮点击: ticketUid=${row.ticketUid}, tktNum=${row.tktNum}`, `票池呼叫按钮点击: ticketUid=${row.ticketUid}, tktNum=${row.tktNum}`,
@ -303,7 +306,10 @@ const handleCall = async (row: Tkt): Promise<void> => {
const handleReEvaluate = async (row: Tkt): Promise<void> => { const handleReEvaluate = async (row: Tkt): Promise<void> => {
window.winControl.windowMinimize() window.winControl.windowMinimize()
window.ticketToMain.triggerEvaluate() window.ticketToMain.triggerEvaluate({
ticketUid: row.ticketUid,
tktNum: row.tktNum,
})
await window.appLogger.log( await window.appLogger.log(
'info', 'info',
`票池评价按钮点击: ticketUid=${row.ticketUid}, tktNum=${row.tktNum}`, `票池评价按钮点击: ticketUid=${row.ticketUid}, tktNum=${row.tktNum}`,

Loading…
Cancel
Save