feat(developer): 完成小程序开发者中心和企业控制台改造

- 设计并实现了开发者中心与企业控制台两大模块
- 按用户角色区分开发者和企业客户,支持多项目类型及成员管理
- 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面
- 实现应用版本发布、消息通知中心、权限审批与开发者申请流程
- 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能
- 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块
- 优化后端API,支持已登录和未注册用户不同加入应用流程
- 前端按钮统一采用微信手机号授权,完善用户授权体验
- 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库
- 增加详细的类型定义文件,提升项目类型安全
- 新增超过55个页面及60个API接口,扩展应用功能和服务体系
- 完成全面的样式设计,实现一致的视觉风格和交互体验
This commit is contained in:
2026-04-13 02:26:46 +08:00
parent 2ae30ac692
commit ffab0ec25c
199 changed files with 20017 additions and 508 deletions

296
src/api/ticket.ts Normal file
View File

@@ -0,0 +1,296 @@
/**
* 工单/技术支持 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<TicketStats> {
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<Ticket | null> {
return mockTickets.find(t => t.id === id) || null;
}
/**
* 获取工单回复列表
*/
export async function listTicketReply(ticketId: string): Promise<TicketReply[]> {
return mockReplies.filter(r => r.ticketId === ticketId);
}
/**
* 创建工单
*/
export async function createTicket(data: Partial<Ticket>): Promise<Ticket> {
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<TicketReply> {
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<void> {
const ticket = mockTickets.find(t => t.id === id);
if (ticket) ticket.status = 'closed';
}
/**
* 评价工单
*/
export async function rateTicket(id: string, rating: number, feedback: string): Promise<void> {
const ticket = mockTickets.find(t => t.id === id);
if (ticket) {
ticket.rating = rating;
ticket.feedback = feedback;
}
}
/**
* 获取工单模板
*/
export async function listTicketTemplate(): Promise<TicketTemplate[]> {
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<void> {
const faq = mockFAQs.find(f => f.id === id);
if (faq) {
if (helpful) faq.helpful++;
else faq.notHelpful++;
}
}