Files
codePortAdmin/src/mock/conversation.ts
2025-12-28 22:09:44 +08:00

336 lines
11 KiB
TypeScript

/**
* 客服会话相关模拟数据
*/
import type {
ConversationAgent,
ConversationListQuery,
ConversationListResult,
ConversationMessage,
ConversationMetrics,
ConversationPriority,
ConversationSession,
ConversationStatus,
ConversationTag
} from '@/types'
const supportAgents: ConversationAgent[] = [
{
id: 11,
name: '林清',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_lin',
title: '资深客服',
workload: 8,
expertise: ['会员权益', '支付问题']
},
{
id: 12,
name: '周晚',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_zhou',
title: '体验顾问',
workload: 5,
expertise: ['产品咨询', '活动政策']
},
{
id: 13,
name: '程斐',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_cheng',
title: '资深专家',
workload: 6,
expertise: ['订单售后', '履约异常']
},
{
id: 14,
name: '白凝',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=agent_bai',
title: '运营支持',
workload: 3,
expertise: ['社群转接', '投诉升级']
}
]
const sessionTags: ConversationTag[] = [
{ id: 1, name: '支付', color: '#1890ff' },
{ id: 2, name: '权益', color: '#722ed1' },
{ id: 3, name: '功能', color: '#13c2c2' },
{ id: 4, name: '投诉', color: '#ff4d4f' },
{ id: 5, name: '活动', color: '#fa8c16' },
{ id: 6, name: 'BUG', color: '#a0d911' }
]
const intents = ['开票需求', '会员续费', '提现异常', '功能咨询', '活动规则', '体验反馈']
const sources = ['App 内反馈', '官网咨询', '小程序客服', '社群转接']
const customerLevels = ['L1', 'L2', 'L3', 'L4', 'L5']
const cities = ['杭州', '上海', '成都', '武汉', '广州', '深圳']
const customerNames = ['南鸢', '时年', '望舒', '青禾', '阿岚', '林舟', '潮生', '江迟', '枝夏', '安意']
const channelPool: Array<ConversationSession['channel']> = ['app', 'web', 'wechat', 'miniapp']
const priorityPool: ConversationPriority[] = ['normal', 'normal', 'high', 'vip']
const statusPool: ConversationStatus[] = ['waiting', 'active', 'pending', 'resolved', 'active', 'pending']
function randomItem<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)]!
}
function randomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function pickTags(): ConversationTag[] {
const count = randomInt(1, 3)
const shuffled = [...sessionTags].sort(() => Math.random() - 0.5)
return shuffled.slice(0, count)
}
function buildMessages(
customerName: string,
customerAvatar: string,
agent?: ConversationAgent,
startTime?: Date
): ConversationMessage[] {
const userMessages = [
'你好,我想咨询一下昨天支付的订单还没有到账是怎么回事?',
'我看到权益说明里有些不一致,麻烦帮我核实下。',
'现在打开功能的时候会提示网络错误,可以帮忙看看吗?',
'关于新的社群活动我有些疑问,规则是不是已经更新了?'
]
const agentMessages = [
'您好,已经为您核实到订单,目前支付渠道反馈处理中,大约 10 分钟内到账。',
'权益说明我们刚刚做了更新,我发一份最新版给您,您稍等查看。',
'收到,我们正在排查这个异常,稍后会第一时间同步处理进度。',
'关于活动规则我帮您确认了,新的版本中需要满足邀请条件才可报名。'
]
const messages: ConversationMessage[] = []
const base = startTime ? new Date(startTime) : new Date(Date.now() - randomInt(10, 72) * 60 * 1000)
let pointer = base.getTime()
const rounds = agent ? randomInt(3, 6) : randomInt(2, 4)
for (let i = 0; i < rounds; i++) {
const userMessage: ConversationMessage = {
id: Number(`${pointer}${i}`),
sender: 'user',
senderName: customerName,
content: randomItem(userMessages),
timestamp: new Date(pointer).toISOString(),
avatar: customerAvatar
}
messages.push(userMessage)
pointer += randomInt(1, 6) * 60 * 1000
if (agent) {
const agentMessage: ConversationMessage = {
id: Number(`${pointer}${i}`),
sender: 'agent',
senderName: agent.name,
content: randomItem(agentMessages),
timestamp: new Date(pointer).toISOString(),
avatar: agent.avatar
}
messages.push(agentMessage)
pointer += randomInt(2, 8) * 60 * 1000
}
}
return messages
}
function generateMockConversations(): ConversationSession[] {
const sessions: ConversationSession[] = []
for (let i = 1; i <= 36; i++) {
const customer = {
id: 2000 + i,
nickname: customerNames[i % customerNames.length]!,
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=session_${i}`,
city: randomItem(cities),
vip: Math.random() > 0.7,
level: randomItem(customerLevels),
intents: [randomItem(intents)]
}
const assignedAgent = Math.random() > 0.2 ? randomItem(supportAgents) : undefined
let status = randomItem(statusPool)
if (!assignedAgent && status !== 'waiting') {
status = 'waiting'
}
const createdAt = new Date(Date.now() - randomInt(1, 7) * 24 * 60 * 60 * 1000)
const messages = buildMessages(customer.nickname, customer.avatar, assignedAgent, createdAt)
const lastMessage = messages[messages.length - 1]!
const firstAgentMessage = messages.find(msg => msg.sender === 'agent')
const priority = randomItem(priorityPool)
sessions.push({
id: i,
sessionCode: `KF${202400 + i}`,
channel: randomItem(channelPool),
status,
priority,
createdAt: messages[0]!.timestamp,
lastMessageAt: lastMessage.timestamp,
lastMessage: lastMessage.content,
unreadCount: status === 'waiting' ? randomInt(1, 6) : Math.max(0, randomInt(0, 3) - 1),
totalMessages: messages.length,
waitingTime: status === 'waiting' ? randomInt(5, 25) : randomInt(1, 10),
firstResponseAt: firstAgentMessage?.timestamp,
resolvedAt: status === 'resolved' ? lastMessage.timestamp : undefined,
satisfaction: status === 'resolved' ? Number((4.2 + Math.random() * 0.7).toFixed(1)) : undefined,
autoDetectedIntent: randomItem(intents),
source: randomItem(sources),
tags: pickTags(),
customer,
assignedAgent: assignedAgent ? { ...assignedAgent } : undefined,
messages
})
}
return sessions.sort((a, b) => new Date(b.lastMessageAt).getTime() - new Date(a.lastMessageAt).getTime())
}
let conversationSessions: ConversationSession[] = generateMockConversations()
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
function buildMetrics(sessions: ConversationSession[]): ConversationMetrics {
const waitingCount = sessions.filter(session => session.status === 'waiting').length
const activeCount = sessions.filter(session => session.status === 'active').length
const pendingCount = sessions.filter(session => session.status === 'pending').length
const resolvedToday = sessions.filter(session => {
if (!session.resolvedAt) return false
const resolvedDate = new Date(session.resolvedAt)
const now = new Date()
return (
resolvedDate.getFullYear() === now.getFullYear() &&
resolvedDate.getMonth() === now.getMonth() &&
resolvedDate.getDate() === now.getDate()
)
}).length
const responseDiffs = sessions
.filter(session => session.firstResponseAt)
.map(session => new Date(session.firstResponseAt!).getTime() - new Date(session.createdAt).getTime())
const satisfactionScores = sessions
.filter(session => session.satisfaction)
.map(session => session.satisfaction!)
const avgFirstResponse =
responseDiffs.length > 0 ? Math.round(responseDiffs.reduce((a, b) => a + b, 0) / responseDiffs.length / 60000) : 0
const satisfaction =
satisfactionScores.length > 0
? Number((satisfactionScores.reduce((a, b) => a + b, 0) / satisfactionScores.length).toFixed(1))
: 0
return {
waitingCount,
activeCount,
pendingCount,
resolvedToday,
satisfaction,
avgFirstResponse
}
}
export async function mockGetConversationList(
params: ConversationListQuery = {}
): Promise<ConversationListResult> {
await delay(250)
const {
page = 1,
pageSize = 10,
keyword,
status = 'all',
channel = 'all',
priority = 'all',
vipOnly,
dateRange
} = params
let filtered = [...conversationSessions]
if (keyword) {
filtered = filtered.filter(session =>
[session.sessionCode, session.customer.nickname, session.lastMessage, session.assignedAgent?.name || '']
.join(' ')
.toLowerCase()
.includes(keyword.toLowerCase())
)
}
if (status !== 'all') {
filtered = filtered.filter(session => session.status === status)
}
if (channel !== 'all') {
filtered = filtered.filter(session => session.channel === channel)
}
if (priority !== 'all') {
filtered = filtered.filter(session => session.priority === priority)
}
if (vipOnly) {
filtered = filtered.filter(session => session.customer.vip)
}
if (dateRange && dateRange.length === 2) {
const [start, end] = dateRange
const startTime = new Date(start).getTime()
const endTime = new Date(end).getTime()
filtered = filtered.filter(session => {
const created = new Date(session.createdAt).getTime()
return created >= startTime && created <= endTime
})
}
const start = (page - 1) * pageSize
const list = filtered.slice(start, start + pageSize)
return {
list,
total: filtered.length,
page,
pageSize,
metrics: buildMetrics(conversationSessions)
}
}
export async function mockUpdateConversationStatus(
sessionId: number,
status: ConversationStatus
): Promise<ConversationSession | undefined> {
await delay(200)
const target = conversationSessions.find(session => session.id === sessionId)
if (!target) return undefined
target.status = status
if (status === 'resolved' || status === 'closed') {
target.resolvedAt = new Date().toISOString()
target.unreadCount = 0
target.waitingTime = 0
}
if (status !== 'waiting' && !target.firstResponseAt) {
target.firstResponseAt = new Date().toISOString()
}
return target
}
export async function mockAssignConversationAgent(
sessionId: number,
agentId: number
): Promise<ConversationSession | undefined> {
await delay(200)
const target = conversationSessions.find(session => session.id === sessionId)
const agent = supportAgents.find(item => item.id === agentId)
if (!target || !agent) return undefined
target.assignedAgent = { ...agent }
if (target.status === 'waiting') {
target.status = 'active'
}
if (!target.firstResponseAt) {
target.firstResponseAt = new Date().toISOString()
}
return target
}
export function mockGetSupportAgents(): ConversationAgent[] {
return supportAgents.map(agent => ({ ...agent }))
}