first commit
This commit is contained in:
335
src/mock/conversation.ts
Normal file
335
src/mock/conversation.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* 客服会话相关模拟数据
|
||||
*/
|
||||
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 }))
|
||||
}
|
||||
Reference in New Issue
Block a user