/** * Service 层 — 统一数据请求入口 * * 页面只调用 service 函数,不直接引用 mock 数据或 wx.request。 * 联调时只需修改此文件内部实现(mock → request),页面代码不动。 * * 当前阶段:全部走 mock * 联调阶段:逐个替换为 request() 调用 */ import { request } from '../utils/request' // ============================================ // Mock 数据导入(联调时逐步删除) // ============================================ import { mockTasks, mockTaskDetails, mockNotes, mockPerformance, mockPerformanceRecords, mockBoardFinance, mockCustomers, mockCoaches, mockCustomerDetail, mockChatMessages, mockChatHistory, } from '../utils/mock-data' import type { Task, TaskDetail, Note, PerformanceData, PerformanceRecord, BoardFinanceData, CustomerCard, CoachCard, CustomerDetail as MockCustomerDetail, ChatMessage, ChatHistoryItem, } from '../utils/mock-data' // ============================================ // 内部工具 // ============================================ /** 模拟网络延迟(联调时删除) */ function delay(ms = 400): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } /** * 是否使用真实 API(联调开关) * 联调时改为 true,或按模块逐个开启 */ const USE_REAL_API = false // ============================================ // 认证模块(已对接真实 API) // ============================================ /** 查询当前用户信息 */ export async function fetchMe(): Promise { return request({ url: '/api/xcx/me', method: 'GET', needAuth: true }) } // ============================================ // 任务模块 // ============================================ export interface FetchTasksParams { status?: 'pending' | 'completed' | 'abandoned' page?: number pageSize?: number } /** 获取任务列表 + 绩效概览 */ export async function fetchTasks(params: FetchTasksParams = {}): Promise<{ tasks: Task[] performance: PerformanceData total: number hasMore: boolean }> { if (USE_REAL_API) { const data = await request({ url: '/api/xcx/tasks', method: 'GET', data: params, needAuth: true, }) // TODO: 联调时映射 snake_case → camelCase return data } await delay() const filtered = params.status ? mockTasks.filter(t => t.status === params.status) : mockTasks return { tasks: filtered, performance: mockPerformance, total: filtered.length, hasMore: false, } } /** 获取任务详情 */ export async function fetchTaskDetail(taskId: string): Promise { if (USE_REAL_API) { return request({ url: `/api/xcx/tasks/${taskId}`, method: 'GET', needAuth: true, }) } await delay() return mockTaskDetails.find(t => t.id === taskId) || mockTaskDetails[0] || null } /** 放弃任务 */ export async function abandonTask(taskId: string, reason: string): Promise { if (USE_REAL_API) { await request({ url: `/api/xcx/tasks/${taskId}/abandon`, method: 'POST', data: { reason }, needAuth: true, }) return } await delay(300) } /** 取消放弃(恢复任务) */ export async function restoreTask(taskId: string): Promise { if (USE_REAL_API) { await request({ url: `/api/xcx/tasks/${taskId}/restore`, method: 'POST', needAuth: true, }) return } await delay(300) } /** 置顶任务 */ export async function pinTask(taskId: string): Promise<{ isPinned: boolean }> { if (USE_REAL_API) { return request({ url: `/api/xcx/tasks/${taskId}/pin`, method: 'POST', needAuth: true, }) } await delay(300) return { isPinned: true } } /** 取消置顶任务 */ export async function unpinTask(taskId: string): Promise<{ isPinned: boolean }> { if (USE_REAL_API) { return request({ url: `/api/xcx/tasks/${taskId}/unpin`, method: 'POST', needAuth: true, }) } await delay(300) return { isPinned: false } } // ============================================ // 备注模块 // ============================================ /** 获取备注列表 */ export async function fetchNotes(params: { page?: number; pageSize?: number } = {}): Promise<{ notes: Note[] total: number hasMore: boolean }> { if (USE_REAL_API) { return request({ url: '/api/xcx/notes', method: 'GET', data: params, needAuth: true }) } await delay() return { notes: mockNotes, total: mockNotes.length, hasMore: false } } /** 新增备注 */ export async function createNote(data: { content: string taskId?: string customerId?: string ratingServiceWillingness?: number ratingRevisitLikelihood?: number }): Promise<{ id: string; createdAt: string }> { if (USE_REAL_API) { return request({ url: '/api/xcx/notes', method: 'POST', data, needAuth: true }) } await delay(300) return { id: `note-${Date.now()}`, createdAt: new Date().toISOString() } } /** 删除备注 */ export async function deleteNote(noteId: string): Promise { if (USE_REAL_API) { await request({ url: `/api/xcx/notes/${noteId}`, method: 'DELETE', needAuth: true }) return } await delay(200) } // ============================================ // 绩效模块 // ============================================ /** 绩效概览 */ export async function fetchPerformanceOverview(params: { year: number month: number }): Promise { if (USE_REAL_API) { return request({ url: '/api/xcx/performance', method: 'GET', data: params, needAuth: true }) } await delay() return mockPerformance } /** 绩效明细(按月) */ export async function fetchPerformanceRecords(params: { year: number month: number page?: number pageSize?: number }): Promise<{ records: PerformanceRecord[]; hasMore: boolean }> { if (USE_REAL_API) { return request({ url: '/api/xcx/performance/records', method: 'GET', data: params, needAuth: true, }) } await delay() return { records: mockPerformanceRecords, hasMore: false } } // ============================================ // 客户模块 // ============================================ /** 客户详情 */ export async function fetchCustomerDetail(customerId: string): Promise { if (USE_REAL_API) { return request({ url: `/api/xcx/customers/${customerId}`, method: 'GET', needAuth: true, }) } await delay() return mockCustomerDetail || null } /** 客户服务记录 */ export async function fetchCustomerRecords(params: { customerId: string year?: number month?: number table?: string }): Promise<{ records: any[]; hasMore: boolean }> { if (USE_REAL_API) { const { customerId, ...rest } = params return request({ url: `/api/xcx/customers/${customerId}/records`, method: 'GET', data: rest, needAuth: true, }) } await delay() // 联调前返回空,页面内联 mock 数据仍生效 return { records: [], hasMore: false } } // ============================================ // 看板模块 // ============================================ /** 助教看板 */ export async function fetchBoardCoaches(params: { skill?: string sort?: string time?: string } = {}): Promise { if (USE_REAL_API) { const data = await request({ url: '/api/xcx/board/coaches', method: 'GET', data: params, needAuth: true, }) return data.items } await delay() return mockCoaches } // CHANGE 2026-03-19 | RNS1.3 T13: 增加 page/pageSize 参数支持分页 /** 客户看板 */ export async function fetchBoardCustomers(params: { dimension?: string project?: string page?: number pageSize?: number } = {}): Promise { if (USE_REAL_API) { const data = await request({ url: '/api/xcx/board/customers', method: 'GET', data: params, needAuth: true, }) return data.items } await delay() return mockCustomers } // CHANGE 2026-03-19 | RNS1.3 T14: 扩展参数为 time/area/compare /** 财务看板 */ export async function fetchBoardFinance(params: { time?: string area?: string compare?: number } = {}): Promise { if (USE_REAL_API) { return request({ url: '/api/xcx/board/finance', method: 'GET', data: params, needAuth: true, }) } await delay() return mockBoardFinance } // ============================================ // 助教模块 // ============================================ /** 助教详情 */ export async function fetchCoachDetail(coachId: string): Promise { if (USE_REAL_API) { return request({ url: `/api/xcx/coaches/${coachId}`, method: 'GET', needAuth: true, }) } await delay() return mockCoaches.find(c => c.id === coachId) || mockCoaches[0] || null } // ============================================ // 对话模块 // ============================================ /** 对话历史列表 */ export async function fetchChatHistory(params: { page?: number pageSize?: number } = {}): Promise<{ items: ChatHistoryItem[]; total: number; hasMore: boolean }> { if (USE_REAL_API) { return request({ url: '/api/xcx/chat/history', method: 'GET', data: params, needAuth: true, }) } await delay() return { items: mockChatHistory, total: mockChatHistory.length, hasMore: false } } /** 对话消息列表 */ export async function fetchChatMessages(chatId: string, params: { page?: number pageSize?: number } = {}): Promise<{ messages: ChatMessage[]; total: number; hasMore: boolean }> { if (USE_REAL_API) { return request({ url: `/api/xcx/chat/${chatId}/messages`, method: 'GET', data: params, needAuth: true, }) } await delay() return { messages: mockChatMessages, total: mockChatMessages.length, hasMore: false } } /** 发送消息 */ export async function sendChatMessage(chatId: string, content: string): Promise<{ userMessage: { id: string; content: string; createdAt: string } aiReply: { id: string; content: string; createdAt: string } }> { if (USE_REAL_API) { return request({ url: `/api/xcx/chat/${chatId}/messages`, method: 'POST', data: { content }, needAuth: true, }) } await delay(800) return { userMessage: { id: `msg-${Date.now()}`, content, createdAt: new Date().toISOString() }, aiReply: { id: `msg-${Date.now() + 1}`, content: '这是 AI 助手的模拟回复,联调后将替换为真实响应。', createdAt: new Date().toISOString(), }, } } // ============================================ // 配置模块 // ============================================ /** 技能类型列表(REQ-1) */ export async function fetchSkillTypes(): Promise> { if (USE_REAL_API) { const data = await request({ url: '/api/xcx/config/skill-types', method: 'GET', needAuth: true, }) return data.skills } // 联调前返回硬编码值,与 board-coach 页面 SKILL_OPTIONS 一致 return [ { value: '', text: '全部' }, { value: 'chinese', text: '中🎱' }, { value: 'snooker', text: '🎯斯诺克' }, { value: 'group', text: '小组课' }, { value: 'tip', text: '打赏课' }, ] }