fix: 登录页校验JWT有效性;同步后台多项功能更新
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
<template>
|
||||
<div class="agent-container">
|
||||
<!-- 客服连接组件(无界面,负责建连) -->
|
||||
<KefuConnect @connected="onConnected" />
|
||||
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="agent-header">
|
||||
<div class="header-left">
|
||||
<el-tag :type="isOnline ? 'success' : 'info'" size="large">
|
||||
{{ isOnline ? '在线' : '离线' }}
|
||||
</el-tag>
|
||||
<el-tag :type="statusTagType" size="large">{{ statusText }}</el-tag>
|
||||
<el-button
|
||||
v-if="goeasyReady"
|
||||
:type="isOnline ? 'danger' : 'primary'"
|
||||
size="small"
|
||||
@click="toggleOnline"
|
||||
@@ -17,6 +13,7 @@
|
||||
>
|
||||
{{ isOnline ? '下线' : '上线' }}
|
||||
</el-button>
|
||||
<span class="online-tip" v-if="goeasyReady && isOnline">切换页面不会下线</span>
|
||||
<span class="unread-tip" v-if="unreadTotal">
|
||||
<i class="el-icon-chat-dot-round" style="color: red; margin-right: 4px;"></i>
|
||||
{{ unreadTotal }}条未读
|
||||
@@ -123,6 +120,7 @@
|
||||
:team-id="teamId"
|
||||
:customer-id="currentCustomerId"
|
||||
:customer-data="currentCustomerData"
|
||||
:agent-online="isOnline"
|
||||
@close="currentCustomerId = ''"
|
||||
/>
|
||||
</div>
|
||||
@@ -132,8 +130,18 @@
|
||||
import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import ChatDialog from '@/components/ChatDialog.vue'
|
||||
import KefuConnect from '@/components/KefuConnect.vue'
|
||||
import GoEasy from 'goeasy'
|
||||
import {
|
||||
KEFU_TEAM_ID,
|
||||
getCsteam,
|
||||
refreshKefuUnread,
|
||||
calcUnreadFromConversations,
|
||||
ensureCustomerAccepted,
|
||||
goOnlineManually,
|
||||
goOfflineManually,
|
||||
isGoeasyConnected,
|
||||
checkCsOnlineStatus
|
||||
} from '@/utils/kefuChat'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
@@ -145,13 +153,14 @@ const availableRoles = [
|
||||
{ prefix: 'Zz', name: '组长' }
|
||||
]
|
||||
|
||||
const isOnline = ref(false)
|
||||
const goeasyReady = ref(isGoeasyConnected())
|
||||
const isOnline = ref(!!window.__kefuCsOnline)
|
||||
const switching = ref(false)
|
||||
const tabActive = ref('pending')
|
||||
const pendingList = ref([])
|
||||
const activeList = ref([])
|
||||
const unreadTotal = ref(0)
|
||||
const teamId = 'support_team'
|
||||
const teamId = KEFU_TEAM_ID
|
||||
|
||||
const searchRole = ref('')
|
||||
const searchUid = ref('')
|
||||
@@ -164,9 +173,18 @@ const defaultAvatar = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726
|
||||
|
||||
const activeRole = ref('all')
|
||||
|
||||
function getCsteam() {
|
||||
if (!window.goeasy) return null
|
||||
return window.goeasy.im.csteam(teamId)
|
||||
const statusText = computed(() => {
|
||||
if (!goeasyReady.value) return '消息连接中…'
|
||||
return isOnline.value ? '在线接待中' : '已离线'
|
||||
})
|
||||
|
||||
const statusTagType = computed(() => {
|
||||
if (!goeasyReady.value) return 'info'
|
||||
return isOnline.value ? 'success' : 'warning'
|
||||
})
|
||||
|
||||
function getCsteamLocal() {
|
||||
return getCsteam()
|
||||
}
|
||||
|
||||
// 过滤
|
||||
@@ -199,8 +217,8 @@ function bindConversationListeners() {
|
||||
conversationsUpdatedHandler = (data) => {
|
||||
const all = data.conversations || []
|
||||
activeList.value = filterConversations(all.filter(c => c.type === 'cs' && !c.ended))
|
||||
unreadTotal.value = data.unreadTotal || 0
|
||||
proxy.$emitter.emit('agent-unread', unreadTotal.value)
|
||||
unreadTotal.value = data.unreadTotal || calcUnreadFromConversations(activeList.value)
|
||||
refreshKefuUnread(proxy.$emitter)
|
||||
}
|
||||
window.goeasy.im.on(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, conversationsUpdatedHandler)
|
||||
|
||||
@@ -218,63 +236,52 @@ const onConnected = () => {
|
||||
fetchActiveConversations()
|
||||
}
|
||||
|
||||
// 上线
|
||||
const csteamOnline = () => {
|
||||
if (!window.goeasy) {
|
||||
console.warn('[Agent] 未建立连接,无法上线')
|
||||
const toggleOnline = () => {
|
||||
if (switching.value || !goeasyReady.value) {
|
||||
ElMessage.warning('消息服务尚未连接,请稍候…')
|
||||
return
|
||||
}
|
||||
const csteam = getCsteam()
|
||||
if (!csteam) return
|
||||
csteam.online({
|
||||
teamData: { name: '阿龙电竞客服中心', avatar: '' },
|
||||
agentData: {
|
||||
name: localStorage.getItem('username') || 'KF',
|
||||
avatar: ''
|
||||
},
|
||||
onSuccess: () => {
|
||||
isOnline.value = true
|
||||
console.log('[Agent] 上线成功')
|
||||
ElMessage.success('已上线,开始接收会话')
|
||||
fetchPendingConversations()
|
||||
fetchActiveConversations()
|
||||
},
|
||||
onFailed: (error) => {
|
||||
console.error('[Agent] 上线失败:', error)
|
||||
ElMessage.error('上线失败:' + error.content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下线
|
||||
const csteamOffline = () => {
|
||||
const csteam = getCsteam()
|
||||
if (!csteam) return
|
||||
csteam.offline({
|
||||
onSuccess: () => {
|
||||
isOnline.value = false
|
||||
console.log('[Agent] 下线成功')
|
||||
ElMessage.success('已下线')
|
||||
pendingList.value = []
|
||||
},
|
||||
onFailed: (error) => {
|
||||
ElMessage.error('下线失败:' + error.content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const toggleOnline = () => {
|
||||
if (switching.value) return
|
||||
switching.value = true
|
||||
if (isOnline.value) {
|
||||
csteamOffline()
|
||||
} else {
|
||||
csteamOnline()
|
||||
const phone = localStorage.getItem('username')
|
||||
const stopSpin = () => { switching.value = false }
|
||||
const safetyTimer = setTimeout(stopSpin, 15000)
|
||||
|
||||
const wrapDone = (fn) => (...args) => {
|
||||
clearTimeout(safetyTimer)
|
||||
stopSpin()
|
||||
fn?.(...args)
|
||||
}
|
||||
|
||||
if (isOnline.value) {
|
||||
goOfflineManually({
|
||||
emitter: proxy.$emitter,
|
||||
onSuccess: wrapDone(() => {
|
||||
isOnline.value = false
|
||||
ElMessage.success('已下线,不再接收新会话')
|
||||
refreshKefuUnread(proxy.$emitter)
|
||||
}),
|
||||
onFailed: wrapDone((error) => {
|
||||
ElMessage.error('下线失败:' + (error?.content || '未知错误'))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
goOnlineManually({
|
||||
phone,
|
||||
emitter: proxy.$emitter,
|
||||
onSuccess: wrapDone(() => {
|
||||
isOnline.value = true
|
||||
ElMessage.success('已上线,开始接收会话')
|
||||
fetchPendingConversations()
|
||||
fetchActiveConversations()
|
||||
refreshKefuUnread(proxy.$emitter)
|
||||
}),
|
||||
onFailed: wrapDone((error) => {
|
||||
ElMessage.error('上线失败:' + (error?.content || '未知错误'))
|
||||
})
|
||||
})
|
||||
}
|
||||
setTimeout(() => { switching.value = false }, 1000)
|
||||
}
|
||||
|
||||
// 拉取待接入
|
||||
const fetchPendingConversations = () => {
|
||||
if (!window.goeasy) return
|
||||
window.goeasy.im.pendingConversations({
|
||||
@@ -295,27 +302,43 @@ const fetchActiveConversations = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const acceptConversation = (conv) => {
|
||||
const acceptAndOpenChat = async (conv, openDialog = true) => {
|
||||
if (activeList.value.length >= 20) {
|
||||
ElMessage.warning('最多同时接入20个会话')
|
||||
return
|
||||
}
|
||||
const csteam = getCsteam()
|
||||
csteam.accept({
|
||||
customer: { id: conv.id, data: conv.data || { name: conv.id, avatar: defaultAvatar } },
|
||||
onSuccess: () => {
|
||||
ElMessage.success('已接入')
|
||||
fetchPendingConversations()
|
||||
fetchActiveConversations()
|
||||
},
|
||||
onFailed: (error) => {
|
||||
ElMessage.error('接入失败:' + error.content)
|
||||
if (!goeasyReady.value) {
|
||||
ElMessage.warning('消息服务连接中,请稍候…')
|
||||
return
|
||||
}
|
||||
if (!isOnline.value) {
|
||||
ElMessage.warning('请先上线后再接入会话')
|
||||
return
|
||||
}
|
||||
const customerId = conv.id || conv.userId
|
||||
const customerData = conv.data || { name: customerId, avatar: defaultAvatar }
|
||||
try {
|
||||
await ensureCustomerAccepted(customerId, customerData)
|
||||
ElMessage.success('已接入')
|
||||
fetchPendingConversations()
|
||||
fetchActiveConversations()
|
||||
if (openDialog) {
|
||||
currentCustomerId.value = customerId
|
||||
currentCustomerData.value = customerData
|
||||
dialogVisible.value = true
|
||||
tabActive.value = 'active'
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
ElMessage.error('接入失败:' + (error?.content || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
const acceptConversation = (conv) => {
|
||||
acceptAndOpenChat(conv, true)
|
||||
}
|
||||
|
||||
const endConversation = (conv) => {
|
||||
const csteam = getCsteam()
|
||||
const csteam = getCsteamLocal()
|
||||
csteam.end({
|
||||
id: conv.id,
|
||||
onSuccess: () => {
|
||||
@@ -328,34 +351,109 @@ const endConversation = (conv) => {
|
||||
})
|
||||
}
|
||||
|
||||
const openChat = (conv) => {
|
||||
currentCustomerId.value = conv.id || conv.userId
|
||||
currentCustomerData.value = conv.data || { name: currentCustomerId.value, avatar: defaultAvatar }
|
||||
dialogVisible.value = true
|
||||
const openChat = async (conv) => {
|
||||
const customerId = conv.id || conv.userId
|
||||
const customerData = conv.data || { name: customerId, avatar: defaultAvatar }
|
||||
if (!goeasyReady.value) {
|
||||
ElMessage.warning('消息服务连接中,请稍候…')
|
||||
return
|
||||
}
|
||||
if (!isOnline.value) {
|
||||
ElMessage.warning('请先上线后再接入会话')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ensureCustomerAccepted(customerId, customerData)
|
||||
currentCustomerId.value = customerId
|
||||
currentCustomerData.value = customerData
|
||||
dialogVisible.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error('接入会话失败:' + (error?.content || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
const startNewChat = () => {
|
||||
if (!searchRole.value || !searchUid.value) return
|
||||
const fullId = searchRole.value + searchUid.value
|
||||
const exists = activeList.value.find(c => c.id === fullId)
|
||||
if (exists) {
|
||||
openChat(exists)
|
||||
if (!goeasyReady.value) {
|
||||
ElMessage.warning('消息服务连接中,请稍候…')
|
||||
return
|
||||
}
|
||||
currentCustomerId.value = fullId
|
||||
currentCustomerData.value = { name: fullId, avatar: defaultAvatar }
|
||||
dialogVisible.value = true
|
||||
if (!isOnline.value) {
|
||||
ElMessage.warning('请先上线后再接入会话')
|
||||
return
|
||||
}
|
||||
|
||||
const uid = String(searchUid.value).trim()
|
||||
if (!/^\d+$/.test(uid)) {
|
||||
ElMessage.warning('UID 请输入纯数字')
|
||||
return
|
||||
}
|
||||
|
||||
const fullId = searchRole.value + uid
|
||||
const active = activeList.value.find(c => (c.id === fullId || c.userId === fullId))
|
||||
if (active) {
|
||||
openChat(active)
|
||||
return
|
||||
}
|
||||
|
||||
const pending = pendingList.value.find(c => c.id === fullId)
|
||||
if (pending) {
|
||||
acceptAndOpenChat(pending, true)
|
||||
return
|
||||
}
|
||||
|
||||
acceptAndOpenChat({
|
||||
id: fullId,
|
||||
data: { name: fullId, avatar: defaultAvatar }
|
||||
}, true)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('[Agent] 页面挂载')
|
||||
const onAgentUnread = (count) => {
|
||||
unreadTotal.value = count
|
||||
}
|
||||
|
||||
const onGoeasyReady = (ready) => {
|
||||
goeasyReady.value = !!ready
|
||||
}
|
||||
|
||||
const onCsOnlineChange = (online) => {
|
||||
isOnline.value = !!online
|
||||
}
|
||||
|
||||
const onGoEasyConnected = () => {
|
||||
onConnected()
|
||||
goeasyReady.value = isGoeasyConnected()
|
||||
onCsOnlineChange(window.__kefuCsOnline)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
goeasyReady.value = isGoeasyConnected()
|
||||
if (goeasyReady.value) {
|
||||
const online = await checkCsOnlineStatus()
|
||||
isOnline.value = online
|
||||
proxy.$emitter.emit('kefu-cs-online', online)
|
||||
} else {
|
||||
isOnline.value = !!window.__kefuCsOnline
|
||||
}
|
||||
proxy.$emitter.on('kefu-goeasy-ready', onGoeasyReady)
|
||||
proxy.$emitter.on('kefu-cs-online', onCsOnlineChange)
|
||||
proxy.$emitter.on('agent-unread', onAgentUnread)
|
||||
if (window.goeasy && isGoeasyConnected()) {
|
||||
onConnected()
|
||||
} else {
|
||||
proxy.$emitter.on('goeasy-connected', onGoEasyConnected)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (conversationsUpdatedHandler) {
|
||||
proxy.$emitter.off('goeasy-connected', onGoEasyConnected)
|
||||
proxy.$emitter.off('kefu-goeasy-ready', onGoeasyReady)
|
||||
proxy.$emitter.off('kefu-cs-online', onCsOnlineChange)
|
||||
proxy.$emitter.off('agent-unread', onAgentUnread)
|
||||
if (window.goeasy && conversationsUpdatedHandler) {
|
||||
window.goeasy.im.off(GoEasy.IM_EVENT.CONVERSATIONS_UPDATED, conversationsUpdatedHandler)
|
||||
}
|
||||
if (pendingUpdatedHandler) {
|
||||
if (window.goeasy && pendingUpdatedHandler) {
|
||||
window.goeasy.im.off(GoEasy.IM_EVENT.PENDING_CONVERSATIONS_UPDATED, pendingUpdatedHandler)
|
||||
}
|
||||
})
|
||||
@@ -439,4 +537,8 @@ onUnmounted(() => {
|
||||
color: #888;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.online-tip {
|
||||
font-size: 12px;
|
||||
color: #8bc34a;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user