/** * 工单/技术支持 API */ import Taro from '@tarojs/taro'; import type { Ticket, TicketReply, TicketTemplate, TicketStats, FAQ } from '../types/ticket'; // 模拟数据 const mockTickets: Ticket[] = [ { id: '1', ticketNo: 'TK202604120001', title: 'API 调用频率限制问题', content: '我在使用批量接口时遇到了 429 错误,请问如何申请提高调用频率限制?', type: 'technical', priority: 'high', status: 'processing', category: 'api', attachments: [], creatorId: '1', creatorName: '张三', creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', assigneeId: 's1', assigneeName: '技术支持小王', assigneeAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', responseCount: 2, solution: '已为您开通企业版调用配额', createTime: '2026-04-12 10:30:00', updateTime: '2026-04-12 14:20:00', resolveTime: 230 }, { id: '2', ticketNo: 'TK202604110002', title: '微信支付回调异常', content: '支付完成后回调地址没有收到通知,请问如何排查问题?', type: 'bug', priority: 'urgent', status: 'resolved', category: 'payment', attachments: ['/uploads/error-log.png'], creatorId: '1', creatorName: '张三', creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', assigneeId: 's2', assigneeName: '技术支持小李', assigneeAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Bobby', responseCount: 5, solution: '回调地址需要配置白名单,已协助配置完成', rating: 5, feedback: '响应很快,问题解决了', resolveTime: 180, createTime: '2026-04-11 09:15:00', updateTime: '2026-04-11 12:15:00', resolveTime2: '2026-04-11 12:15:00' }, { id: '3', ticketNo: 'TK202604100003', title: '功能建议:支持 Webhook 重试机制', content: '希望 Webhook 能够支持失败重试功能,提高消息可靠性', type: 'feature', priority: 'medium', status: 'pending', category: 'api', attachments: [], creatorId: '1', creatorName: '张三', creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', responseCount: 0, createTime: '2026-04-10 16:45:00', updateTime: '2026-04-10 16:45:00' } ]; const mockReplies: TicketReply[] = [ { id: 'r1', ticketId: '1', content: '您好,请问您的应用日调用量是多少?企业版默认配额为 10万次/天', attachments: [], isInternal: false, senderId: 's1', senderName: '技术支持小王', senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', senderRole: 'support', createTime: '2026-04-12 11:00:00' }, { id: 'r2', ticketId: '1', content: '我的日调用量大约在 15 万次左右', attachments: [], isInternal: false, senderId: '1', senderName: '张三', senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', senderRole: 'user', createTime: '2026-04-12 11:30:00' }, { id: 'r3', ticketId: '1', content: '已为您升级到企业高级版,配额提升至 50万次/天', attachments: [], isInternal: false, senderId: 's1', senderName: '技术支持小王', senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', senderRole: 'support', createTime: '2026-04-12 14:20:00' } ]; const mockTemplates: TicketTemplate[] = [ { id: 't1', title: 'API 调用问题', content: '【问题描述】\n\n【复现步骤】\n1.\n2.\n3.\n\n【错误信息】\n\n【环境信息】', type: 'technical', category: 'api', priority: 'medium' }, { id: 't2', title: '支付相关问题', content: '【问题类型】\n□ 支付失败 □ 退款 □ 发票\n\n【订单号】\n\n【问题描述】', type: 'billing', category: 'payment', priority: 'high' }, { id: 't3', title: 'Bug 反馈', content: '【Bug 标题】\n\n【影响范围】\n\n【复现步骤】\n1.\n2.\n\n【预期行为】\n\n【实际行为】', type: 'bug', category: 'other', priority: 'high' } ]; const mockFAQs: FAQ[] = [ { id: 'f1', question: '如何获取 API Key?', answer: '登录控制台 -> 开发管理 -> API Key -> 创建 Key', category: 'api', viewCount: 1256, helpful: 234, notHelpful: 12, tags: ['API', '密钥'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-01' }, { id: 'f2', question: '调用频率限制是多少?', answer: '免费版 1000次/天,个人版 1万次/天,企业版 10万次/天', category: 'api', viewCount: 2345, helpful: 456, notHelpful: 23, tags: ['限流', '配额'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-05' }, { id: 'f3', question: '如何申请发票?', answer: '控制台 -> 财务 -> 发票管理 -> 申请发票', category: 'billing', viewCount: 1890, helpful: 345, notHelpful: 15, tags: ['发票', '财务'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-03-20' }, { id: 'f4', question: 'Webhook 回调失败怎么办?', answer: '1. 检查回调地址是否可公网访问\n2. 确保返回 200 状态码\n3. 检查签名验证', category: 'api', viewCount: 1567, helpful: 289, notHelpful: 34, tags: ['Webhook', '回调'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-10' }, { id: 'f5', question: '如何升级账户?', answer: '控制台 -> 套餐管理 -> 选择套餐 -> 在线支付', category: 'account', viewCount: 987, helpful: 178, notHelpful: 8, tags: ['升级', '套餐'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-03-15' } ]; /** * 获取工单统计 */ export async function getTicketStats(): Promise { return { total: mockTickets.length, pending: 1, processing: 1, resolved: 1, avgResponseTime: 45, avgResolveTime: 4.2, satisfaction: 4.8 }; } /** * 分页获取工单列表 */ export async function pageTicket(params: { status?: string; type?: string; priority?: string; keyword?: string; page?: number; pageSize?: number; }): Promise<{ list: Ticket[]; total: number }> { const { status, type, priority, keyword, page = 1, pageSize = 10 } = params; let filtered = [...mockTickets]; if (status) filtered = filtered.filter(t => t.status === status); if (type) filtered = filtered.filter(t => t.type === type); if (priority) filtered = filtered.filter(t => t.priority === priority); if (keyword) { const kw = keyword.toLowerCase(); filtered = filtered.filter(t => t.title.toLowerCase().includes(kw) || t.content.toLowerCase().includes(kw)); } const start = (page - 1) * pageSize; return { list: filtered.slice(start, start + pageSize), total: filtered.length }; } /** * 获取工单详情 */ export async function getTicketDetail(id: string): Promise { return mockTickets.find(t => t.id === id) || null; } /** * 获取工单回复列表 */ export async function listTicketReply(ticketId: string): Promise { return mockReplies.filter(r => r.ticketId === ticketId); } /** * 创建工单 */ export async function createTicket(data: Partial): Promise { const newTicket: Ticket = { id: String(Date.now()), ticketNo: `TK${new Date().toISOString().replace(/[\-:T]/g, '').slice(0, 12)}`, title: data.title || '', content: data.content || '', type: data.type || 'technical', priority: data.priority || 'medium', status: 'pending', category: data.category || 'api', attachments: data.attachments || [], creatorId: '1', creatorName: '张三', creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', responseCount: 0, createTime: new Date().toISOString().replace('T', ' ').slice(0, 19), updateTime: new Date().toISOString().replace('T', ' ').slice(0, 19) }; mockTickets.unshift(newTicket); return newTicket; } /** * 回复工单 */ export async function replyTicket(ticketId: string, content: string, attachments: string[] = []): Promise { const reply: TicketReply = { id: String(Date.now()), ticketId, content, attachments, isInternal: false, senderId: '1', senderName: '张三', senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', senderRole: 'user', createTime: new Date().toISOString().replace('T', ' ').slice(0, 19) }; mockReplies.push(reply); const ticket = mockTickets.find(t => t.id === ticketId); if (ticket) { ticket.responseCount++; ticket.updateTime = reply.createTime; if (ticket.status === 'pending') ticket.status = 'processing'; } return reply; } /** * 关闭工单 */ export async function closeTicket(id: string): Promise { const ticket = mockTickets.find(t => t.id === id); if (ticket) ticket.status = 'closed'; } /** * 评价工单 */ export async function rateTicket(id: string, rating: number, feedback: string): Promise { const ticket = mockTickets.find(t => t.id === id); if (ticket) { ticket.rating = rating; ticket.feedback = feedback; } } /** * 获取工单模板 */ export async function listTicketTemplate(): Promise { return mockTemplates; } /** * 获取 FAQ 列表 */ export async function pageFAQ(params: { category?: string; keyword?: string; page?: number; pageSize?: number; }): Promise<{ list: FAQ[]; total: number }> { const { category, keyword, page = 1, pageSize = 10 } = params; let filtered = [...mockFAQs]; if (category) filtered = filtered.filter(f => f.category === category); if (keyword) { const kw = keyword.toLowerCase(); filtered = filtered.filter(f => f.question.toLowerCase().includes(kw) || f.answer.toLowerCase().includes(kw)); } const start = (page - 1) * pageSize; return { list: filtered.slice(start, start + pageSize), total: filtered.length }; } /** * FAQ 反馈 */ export async function feedbackFAQ(id: string, helpful: boolean): Promise { const faq = mockFAQs.find(f => f.id === id); if (faq) { if (helpful) faq.helpful++; else faq.notHelpful++; } }