/** * 客服会话相关模拟数据 */ 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 = ['app', 'web', 'wechat', 'miniapp'] const priorityPool: ConversationPriority[] = ['normal', 'normal', 'high', 'vip'] const statusPool: ConversationStatus[] = ['waiting', 'active', 'pending', 'resolved', 'active', 'pending'] function randomItem(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 { 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 { 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 { 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 { 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 })) }