|
|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import {
|
|
|
|
|
CircleCheck,
|
|
|
|
|
Delete,
|
|
|
|
|
Menu,
|
|
|
|
|
Phone,
|
|
|
|
|
Star,
|
|
|
|
|
@ -9,7 +10,7 @@ import {
|
|
|
|
|
VideoPlay,
|
|
|
|
|
} from '@element-plus/icons-vue'
|
|
|
|
|
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 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}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>({
|
|
|
|
|
empUid: -1,
|
|
|
|
|
winUid: -1,
|
|
|
|
|
@ -32,9 +68,31 @@ const callStatus = ref('idle')
|
|
|
|
|
const callBtnText = ref('呼叫')
|
|
|
|
|
const pauseBtnText = ref('暂停')
|
|
|
|
|
const callingTkt = ref(-1)
|
|
|
|
|
// 由 Login.vue 缓存的窗口 UID(用于 /getQueueCount 参数)
|
|
|
|
|
const cachedWindowUid = ref(-1)
|
|
|
|
|
|
|
|
|
|
const EVALUATING_COUNTDOWN_SEC = 15
|
|
|
|
|
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 {
|
|
|
|
|
if (evaluatingCountdownTimer !== null) {
|
|
|
|
|
@ -49,6 +107,7 @@ function clearEvaluatingCountdown(): void {
|
|
|
|
|
*/
|
|
|
|
|
function startEvaluatingCountdown(prefixText: string): void {
|
|
|
|
|
clearEvaluatingCountdown()
|
|
|
|
|
evaluatingPrefixText.value = prefixText
|
|
|
|
|
let left = EVALUATING_COUNTDOWN_SEC
|
|
|
|
|
const applyMessage = (): void => {
|
|
|
|
|
message.value = `${prefixText}(剩余 ${left} 秒)`
|
|
|
|
|
@ -58,18 +117,102 @@ function startEvaluatingCountdown(prefixText: string): void {
|
|
|
|
|
evaluatingCountdownTimer = setInterval(() => {
|
|
|
|
|
left -= 1
|
|
|
|
|
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()
|
|
|
|
|
callStatus.value = 'idle'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
callingTkt.value = -1
|
|
|
|
|
message.value = '欢迎使用紫云呼叫终端'
|
|
|
|
|
void window.appLogger.log('info', '评价倒计时结束,进入待机')
|
|
|
|
|
await window.appLogger.log('info', 'isRank: 评价完成,进入待机')
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:每秒轮询 /isRank;idle:每 15 秒轮询 /getQueueCount
|
|
|
|
|
watch(
|
|
|
|
|
callStatus,
|
|
|
|
|
status => {
|
|
|
|
|
if (status === 'evaluating') {
|
|
|
|
|
startIsRankPolling()
|
|
|
|
|
} else {
|
|
|
|
|
clearIsRankPolling()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status === 'idle') {
|
|
|
|
|
startQueueCountPolling()
|
|
|
|
|
} else {
|
|
|
|
|
clearQueueCountPolling()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 将 buttons 的定义改为 computed;「开始」与「完成」占同一槽位:working 时只显示「完成」,其余状态显示「开始」
|
|
|
|
|
const buttons = computed(() => {
|
|
|
|
|
const startOrComplete =
|
|
|
|
|
@ -99,6 +242,12 @@ const buttons = computed(() => {
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
startOrComplete,
|
|
|
|
|
{
|
|
|
|
|
icon: Delete,
|
|
|
|
|
label: '弃号',
|
|
|
|
|
action: 'abandon',
|
|
|
|
|
enabled: callStatus.value === 'calling',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: Switch,
|
|
|
|
|
label: '转移',
|
|
|
|
|
@ -144,8 +293,14 @@ const callAction = async (): Promise<void> => {
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
updateLog(`已重呼:${res.ticketNo},请勿重复点击!`)
|
|
|
|
|
await window.appLogger.log('info', `重呼成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
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
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
@ -156,27 +311,30 @@ const callAction = async (): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
const w = sessionState.value.winUid || -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({
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 呼叫成功后的处理
|
|
|
|
|
if (res.success === true) {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
console.log('call success')
|
|
|
|
|
callStatus.value = 'calling'
|
|
|
|
|
callBtnText.value = '重呼'
|
|
|
|
|
callingTkt.value = res.ticketUid
|
|
|
|
|
updateLog(`正在呼叫:${res.ticketNo}`)
|
|
|
|
|
callingTkt.value = getActionTicketUid(res)
|
|
|
|
|
updateLog(`正在呼叫:${getActionTicketNo(res)}`)
|
|
|
|
|
await window.appLogger.log(
|
|
|
|
|
'info',
|
|
|
|
|
`呼叫成功: ticketNo=${res.ticketNo}, ticketUid=${res.ticketUid}`,
|
|
|
|
|
`呼叫成功: ticketNo=${getActionTicketNo(res)}, ticketUid=${getActionTicketUid(res)}`,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// 刷新日志
|
|
|
|
|
updateLog(res.message)
|
|
|
|
|
await window.appLogger.log('warn', `呼叫未成功: ${res.message}`)
|
|
|
|
|
const m = getActionMessage(res)
|
|
|
|
|
updateLog(m)
|
|
|
|
|
await window.appLogger.log('warn', `呼叫未成功: ${m}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (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
|
|
|
|
|
const startAction = async (): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
@ -196,12 +384,13 @@ const startAction = async (): Promise<void> => {
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
callStatus.value = 'working'
|
|
|
|
|
updateLog(`正在办理:${res.ticketNo}`)
|
|
|
|
|
await window.appLogger.log('info', `开始办理成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
updateLog(`正在办理:${getActionTicketNo(res)}`)
|
|
|
|
|
await window.appLogger.log('info', `开始办理成功: ticketNo=${getActionTicketNo(res)}`)
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `开始办理未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
const m = getActionMessage(res) || 'unknown'
|
|
|
|
|
await window.appLogger.log('warn', `开始办理未成功: ${m}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
@ -221,15 +410,16 @@ const completeAction = async (): Promise<void> => {
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
if (!res.success) {
|
|
|
|
|
await window.appLogger.log('warn', `完成办理未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
if (!isActionSuccess(res)) {
|
|
|
|
|
const m = getActionMessage(res) || 'unknown'
|
|
|
|
|
await window.appLogger.log('warn', `完成办理未成功: ${m}`)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await window.appLogger.log('info', `完成办理成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
await window.appLogger.log('info', `完成办理成功: ticketNo=${getActionTicketNo(res)}`)
|
|
|
|
|
callStatus.value = 'evaluating'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
startEvaluatingCountdown(`办理完成:${res.ticketNo},评价中`)
|
|
|
|
|
startEvaluatingCountdown(`办理完成:${getActionTicketNo(res)},评价中`)
|
|
|
|
|
await window.appLogger.log('info', '完成办理成功,进入评价中状态')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
@ -302,13 +492,14 @@ const pauseAction = async (): Promise<void> => {
|
|
|
|
|
empUid: e,
|
|
|
|
|
pauseReason: reason,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
callStatus.value = 'paused'
|
|
|
|
|
pauseBtnText.value = '恢复'
|
|
|
|
|
updateLog(`暂停中,原因:${reason}`)
|
|
|
|
|
await window.appLogger.log('info', `暂停成功: reason=${reason}`)
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `暂停未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
const m = getActionMessage(res) || 'unknown'
|
|
|
|
|
await window.appLogger.log('warn', `暂停未成功: ${m}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await logErr('暂停失败', err)
|
|
|
|
|
@ -323,12 +514,13 @@ const pauseAction = async (): Promise<void> => {
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
if (isActionSuccess(res)) {
|
|
|
|
|
callStatus.value = 'idle'
|
|
|
|
|
pauseBtnText.value = '暂停'
|
|
|
|
|
await window.appLogger.log('info', '恢复成功')
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `恢复未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
const m = getActionMessage(res) || 'unknown'
|
|
|
|
|
await window.appLogger.log('warn', `恢复未成功: ${m}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await logErr('恢复失败', err)
|
|
|
|
|
@ -345,6 +537,9 @@ const handleButtonClick = (button: ActionButton): void => {
|
|
|
|
|
case 'call':
|
|
|
|
|
callAction()
|
|
|
|
|
break
|
|
|
|
|
case 'abandon':
|
|
|
|
|
abandonAction()
|
|
|
|
|
break
|
|
|
|
|
case 'pause':
|
|
|
|
|
pauseAction()
|
|
|
|
|
break
|
|
|
|
|
@ -374,8 +569,24 @@ const updateLog = (log: string): void => {
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
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 触发
|
|
|
|
|
window.mainTicketEvents.onAction(action => {
|
|
|
|
|
window.mainTicketEvents.onAction((action, payload) => {
|
|
|
|
|
if (typeof payload?.ticketUid === 'number' && payload.ticketUid > 0) {
|
|
|
|
|
callingTkt.value = payload.ticketUid
|
|
|
|
|
}
|
|
|
|
|
if (action === 'call') {
|
|
|
|
|
void callAction()
|
|
|
|
|
return
|
|
|
|
|
@ -389,6 +600,8 @@ onMounted(async () => {
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
clearEvaluatingCountdown()
|
|
|
|
|
clearIsRankPolling()
|
|
|
|
|
clearQueueCountPolling()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|