|
|
|
|
@ -1,10 +1,23 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { Menu, Phone, Star, Switch, VideoPause, VideoPlay } from '@element-plus/icons-vue'
|
|
|
|
|
import {
|
|
|
|
|
CircleCheck,
|
|
|
|
|
Menu,
|
|
|
|
|
Phone,
|
|
|
|
|
Star,
|
|
|
|
|
Switch,
|
|
|
|
|
VideoPause,
|
|
|
|
|
VideoPlay,
|
|
|
|
|
} from '@element-plus/icons-vue'
|
|
|
|
|
import { SessionState } from 'src/shared/types/session'
|
|
|
|
|
import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
import { api } from '../api'
|
|
|
|
|
import type { ActionButton } from '../types/action'
|
|
|
|
|
|
|
|
|
|
const logErr = async (ctx: string, error: unknown): Promise<void> => {
|
|
|
|
|
const msg = error instanceof Error ? error.message : String(error)
|
|
|
|
|
await window.appLogger.log('error', `${ctx}: ${msg}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sessionState = ref<SessionState>({
|
|
|
|
|
empUid: -1,
|
|
|
|
|
winUid: -1,
|
|
|
|
|
@ -20,50 +33,96 @@ const callBtnText = ref('呼叫')
|
|
|
|
|
const pauseBtnText = ref('暂停')
|
|
|
|
|
const callingTkt = ref(-1)
|
|
|
|
|
|
|
|
|
|
// 将 buttons 的定义改为 computed,这样它会随着 callStatus 和 pauseBtnText 的变化而自动更新
|
|
|
|
|
const buttons = computed(() => [
|
|
|
|
|
// { icon: Menu, label: '菜单', color: iconColor, action: 'showMenu' }, // 新增自定义菜单按钮
|
|
|
|
|
{
|
|
|
|
|
icon: Phone,
|
|
|
|
|
label: callBtnText.value,
|
|
|
|
|
action: 'call',
|
|
|
|
|
enabled: !['connected', 'paused', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: VideoPlay,
|
|
|
|
|
label: '开始',
|
|
|
|
|
action: 'start',
|
|
|
|
|
enabled: !['idle', 'paused', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: Switch,
|
|
|
|
|
label: '转移',
|
|
|
|
|
action: 'transfer',
|
|
|
|
|
enabled: !['idle', 'calling', 'paused', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: VideoPause,
|
|
|
|
|
label: pauseBtnText.value,
|
|
|
|
|
action: 'pause',
|
|
|
|
|
enabled: !['calling', 'calling', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: Star,
|
|
|
|
|
label: '评价',
|
|
|
|
|
action: 'evaluate',
|
|
|
|
|
enabled: !['idle', 'calling', 'paused', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
const EVALUATING_COUNTDOWN_SEC = 15
|
|
|
|
|
let evaluatingCountdownTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
|
|
|
|
|
|
function clearEvaluatingCountdown(): void {
|
|
|
|
|
if (evaluatingCountdownTimer !== null) {
|
|
|
|
|
clearInterval(evaluatingCountdownTimer)
|
|
|
|
|
evaluatingCountdownTimer = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 进入 evaluating 后启动 15 秒倒计时,文案显示在 log-span(message)
|
|
|
|
|
* @param prefixText 倒计时前缀,例如「办理完成:xxx,评价中」或「评价中」
|
|
|
|
|
*/
|
|
|
|
|
function startEvaluatingCountdown(prefixText: string): void {
|
|
|
|
|
clearEvaluatingCountdown()
|
|
|
|
|
let left = EVALUATING_COUNTDOWN_SEC
|
|
|
|
|
const applyMessage = (): void => {
|
|
|
|
|
message.value = `${prefixText}(剩余 ${left} 秒)`
|
|
|
|
|
}
|
|
|
|
|
applyMessage()
|
|
|
|
|
|
|
|
|
|
evaluatingCountdownTimer = setInterval(() => {
|
|
|
|
|
left -= 1
|
|
|
|
|
if (left <= 0) {
|
|
|
|
|
clearEvaluatingCountdown()
|
|
|
|
|
callStatus.value = 'idle'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
callingTkt.value = -1
|
|
|
|
|
message.value = '欢迎使用紫云呼叫终端'
|
|
|
|
|
void window.appLogger.log('info', '评价倒计时结束,进入待机')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
applyMessage()
|
|
|
|
|
}, 1000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将 buttons 的定义改为 computed;「开始」与「完成」占同一槽位:working 时只显示「完成」,其余状态显示「开始」
|
|
|
|
|
const buttons = computed(() => {
|
|
|
|
|
const startOrComplete =
|
|
|
|
|
callStatus.value === 'working'
|
|
|
|
|
? {
|
|
|
|
|
icon: CircleCheck,
|
|
|
|
|
label: '完成',
|
|
|
|
|
action: 'complete',
|
|
|
|
|
enabled: true,
|
|
|
|
|
}
|
|
|
|
|
: {
|
|
|
|
|
icon: VideoPlay,
|
|
|
|
|
label: '开始',
|
|
|
|
|
action: 'start',
|
|
|
|
|
enabled: !['idle', 'paused', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
icon: Phone,
|
|
|
|
|
label: callBtnText.value,
|
|
|
|
|
action: 'call',
|
|
|
|
|
enabled: !['connected', 'paused', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
startOrComplete,
|
|
|
|
|
{
|
|
|
|
|
icon: Switch,
|
|
|
|
|
label: '转移',
|
|
|
|
|
action: 'transfer',
|
|
|
|
|
enabled: !['idle', 'calling', 'paused', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: VideoPause,
|
|
|
|
|
label: pauseBtnText.value,
|
|
|
|
|
action: 'pause',
|
|
|
|
|
enabled: !['calling', 'calling', 'working', 'evaluating', 'transferring'].includes(
|
|
|
|
|
callStatus.value,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
icon: Star,
|
|
|
|
|
label: '评价',
|
|
|
|
|
action: 'evaluate',
|
|
|
|
|
enabled: !['idle', 'calling', 'paused', 'working', 'transferring'].includes(callStatus.value),
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 打开应用菜单
|
|
|
|
|
const openMoreMenu = (): void => {
|
|
|
|
|
@ -76,23 +135,31 @@ const callAction = async (): Promise<void> => {
|
|
|
|
|
// 重呼
|
|
|
|
|
if (callStatus.value === 'calling' && callingTkt.value > 0) {
|
|
|
|
|
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}, ticketUid=${t}`)
|
|
|
|
|
const res = await api.action.recall({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
ticketUid: callingTkt.value,
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
updateLog(`已重呼:${res.ticketNo},请勿重复点击!`)
|
|
|
|
|
await window.appLogger.log('info', `重呼成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
return
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('重呼失败', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 呼叫
|
|
|
|
|
const w = sessionState.value.winUid || -1
|
|
|
|
|
const e = sessionState.value.empUid || -1
|
|
|
|
|
await window.appLogger.log('info', `请求呼叫: windowUid=${w}, empUid=${e}`)
|
|
|
|
|
const res = await api.action.call({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 呼叫成功后的处理
|
|
|
|
|
@ -102,46 +169,114 @@ const callAction = async (): Promise<void> => {
|
|
|
|
|
callBtnText.value = '重呼'
|
|
|
|
|
callingTkt.value = res.ticketUid
|
|
|
|
|
updateLog(`正在呼叫:${res.ticketNo}`)
|
|
|
|
|
await window.appLogger.log(
|
|
|
|
|
'info',
|
|
|
|
|
`呼叫成功: ticketNo=${res.ticketNo}, ticketUid=${res.ticketUid}`,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// 刷新日志
|
|
|
|
|
updateLog(res.message)
|
|
|
|
|
await window.appLogger.log('warn', `呼叫未成功: ${res.message}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('呼叫失败', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 开始办理action
|
|
|
|
|
const startAction = 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}, ticketUid=${t}`)
|
|
|
|
|
const res = await api.action.start({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
ticketUid: callingTkt.value,
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'working'
|
|
|
|
|
updateLog(`正在办理:${res.ticketNo}`)
|
|
|
|
|
await window.appLogger.log('info', `开始办理成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `开始办理未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('开始办理失败', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 评价action
|
|
|
|
|
const evaluateAction = async (): Promise<void> => {
|
|
|
|
|
// 完成办理(与 start 相同参数);成功后立即进入 evaluating
|
|
|
|
|
const completeAction = async (): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await api.action.evaluate({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
ticketUid: callingTkt.value,
|
|
|
|
|
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}, ticketUid=${t}`)
|
|
|
|
|
const res = await api.action.complete({
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'evaluating'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
if (!res.success) {
|
|
|
|
|
await window.appLogger.log('warn', `完成办理未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await window.appLogger.log('info', `完成办理成功: ticketNo=${res.ticketNo}`)
|
|
|
|
|
callStatus.value = 'evaluating'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
startEvaluatingCountdown(`办理完成:${res.ticketNo},评价中`)
|
|
|
|
|
await window.appLogger.log('info', '完成办理成功,进入评价中状态')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('完成办理失败', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 使用当前 session 与 ticket 调用 evaluate(与首次评价相同参数) */
|
|
|
|
|
const invokeEvaluateApi = async (): Promise<void> => {
|
|
|
|
|
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}, ticketUid=${t}`)
|
|
|
|
|
const res = await api.action.evaluate({
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
ticketUid: t,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'evaluating'
|
|
|
|
|
callBtnText.value = '呼叫'
|
|
|
|
|
startEvaluatingCountdown('评价中')
|
|
|
|
|
await window.appLogger.log('info', '评价请求成功')
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `评价未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 评价action;evaluating 下点击需二次确认后重新发起 evaluate
|
|
|
|
|
const evaluateAction = async (): Promise<void> => {
|
|
|
|
|
if (callStatus.value === 'evaluating') {
|
|
|
|
|
const ok = await window.nativeDialog.confirm({
|
|
|
|
|
title: '提示',
|
|
|
|
|
message: '当前业务正在评价中,是否重新发起评价?',
|
|
|
|
|
okLabel: '是',
|
|
|
|
|
cancelLabel: '否',
|
|
|
|
|
})
|
|
|
|
|
if (!ok) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await invokeEvaluateApi()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('评价失败', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 暂停action
|
|
|
|
|
@ -152,32 +287,56 @@ const pauseAction = async (): Promise<void> => {
|
|
|
|
|
window.pauseMenu.showPauseMenu()
|
|
|
|
|
|
|
|
|
|
// 暂停菜单回调
|
|
|
|
|
window.pauseMenu.pauseReasonSelected(async (action: string) => {
|
|
|
|
|
console.log('pauseReasonSelected', action)
|
|
|
|
|
window.pauseMenu.pauseReasonSelected(async (reason: string) => {
|
|
|
|
|
console.log('pauseReasonSelected', reason)
|
|
|
|
|
|
|
|
|
|
const res = await api.action.pause({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
pauseReason: action,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'paused'
|
|
|
|
|
pauseBtnText.value = '恢复'
|
|
|
|
|
updateLog(`暂停中,原因:${action}`)
|
|
|
|
|
try {
|
|
|
|
|
const w = sessionState.value.winUid || -1
|
|
|
|
|
const e = sessionState.value.empUid || -1
|
|
|
|
|
await window.appLogger.log(
|
|
|
|
|
'info',
|
|
|
|
|
`请求暂停: windowUid=${w}, empUid=${e}, pauseReason=${reason}`,
|
|
|
|
|
)
|
|
|
|
|
const res = await api.action.pause({
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
pauseReason: reason,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'paused'
|
|
|
|
|
pauseBtnText.value = '恢复'
|
|
|
|
|
updateLog(`暂停中,原因:${reason}`)
|
|
|
|
|
await window.appLogger.log('info', `暂停成功: reason=${reason}`)
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `暂停未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await logErr('暂停失败', err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
const res = await api.action.resume({
|
|
|
|
|
windowUid: sessionState.value.winUid || -1,
|
|
|
|
|
empUid: sessionState.value.empUid || -1,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'idle'
|
|
|
|
|
pauseBtnText.value = '暂停'
|
|
|
|
|
try {
|
|
|
|
|
const w = sessionState.value.winUid || -1
|
|
|
|
|
const e = sessionState.value.empUid || -1
|
|
|
|
|
await window.appLogger.log('info', `请求恢复: windowUid=${w}, empUid=${e}`)
|
|
|
|
|
const res = await api.action.resume({
|
|
|
|
|
windowUid: w,
|
|
|
|
|
empUid: e,
|
|
|
|
|
})
|
|
|
|
|
if (res.success) {
|
|
|
|
|
callStatus.value = 'idle'
|
|
|
|
|
pauseBtnText.value = '暂停'
|
|
|
|
|
await window.appLogger.log('info', '恢复成功')
|
|
|
|
|
} else {
|
|
|
|
|
await window.appLogger.log('warn', `恢复未成功: ${res.message ?? 'unknown'}`)
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
await logErr('恢复失败', err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('error', error)
|
|
|
|
|
await logErr('暂停/恢复流程异常', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 菜单按钮点击事件
|
|
|
|
|
@ -191,10 +350,14 @@ const handleButtonClick = (button: ActionButton): void => {
|
|
|
|
|
break
|
|
|
|
|
case 'transfer':
|
|
|
|
|
console.log('transfer')
|
|
|
|
|
void window.appLogger.log('info', '转移: 当前未对接接口')
|
|
|
|
|
break
|
|
|
|
|
case 'start':
|
|
|
|
|
startAction()
|
|
|
|
|
break
|
|
|
|
|
case 'complete':
|
|
|
|
|
completeAction()
|
|
|
|
|
break
|
|
|
|
|
case 'evaluate':
|
|
|
|
|
evaluateAction()
|
|
|
|
|
break
|
|
|
|
|
@ -210,6 +373,22 @@ const updateLog = (log: string): void => {
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
sessionState.value = await window.session.get()
|
|
|
|
|
|
|
|
|
|
// TicketList -> Main.vue 触发
|
|
|
|
|
window.mainTicketEvents.onAction(action => {
|
|
|
|
|
if (action === 'call') {
|
|
|
|
|
void callAction()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (action === 'evaluate') {
|
|
|
|
|
void evaluateAction()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
clearEvaluatingCountdown()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|