// AI_CHANGELOG // - 2026-03-23 | Prompt: Task 6 Change B | fetchTasks 增加响应适配层(items→tasks, // 计算 hasMore, 透传 performance),对齐后端 /api/xcx/tasks 返回格式。 // - 2026-03-20 | Prompt: R3 项目类型筛选接口重建 | fetchSkillTypes() fallback 数据 // value 从 all/chinese/snooker 改为 ALL/BILLIARD/SNOOKER;API 响应映射从 // data.skills 改为直接 map data 数组(后端返回 [{key,label,emoji,cls}])。 // - 2026-03-20 | Prompt: RNS1.4 T12.1 移除 mock 数据残留 | 移除所有 mock 导入、 // USE_REAL_API 开关、delay() 辅助函数和 mock fallback 分支,全部直连真实 API。 /** * Service 层 — 统一数据请求入口 * * 页面只调用 service 函数,不直接引用 mock 数据或 wx.request。 * 所有函数均直连真实后端 API。 */ import { request } from '../utils/request' import type { Task, TaskDetail, Note, PerformanceData, BoardFinanceData, CustomerCard, CoachCard, CustomerDetail, } from '../utils/mock-data' // ============================================ // 认证模块 // ============================================ /** 查询当前用户信息 */ export async function fetchMe(): Promise { return request({ url: '/api/xcx/me', method: 'GET', needAuth: true }) } // ============================================ // 业务时钟(沙箱支持) // ============================================ export interface RuntimeClock { mode: 'live' | 'sandbox' business_date: string // YYYY-MM-DD business_year: number business_month: number business_year_month: string // YYYY-MM business_now: string is_sandbox: boolean sandbox_date: string | null sandbox_instance_id: string | null } /** * 获取当前门店的业务时钟(live 真实日,sandbox 模拟日)。 * 沙箱模式下,小程序所有依赖"当前年月"的请求都应使用此结果, * 避免直接 ``new Date()`` 导致与后端 sandbox_date 不一致。 */ export async function fetchRuntimeClock(): Promise { return request({ url: '/api/xcx/runtime/clock', method: 'GET', needAuth: true }) } // ============================================ // 任务模块 // ============================================ export interface FetchTasksParams { status?: 'pending' | 'completed' | 'abandoned' page?: number pageSize?: number } /** 获取任务列表 + 绩效概览 */ // CHANGE 2026-03-23 | Task 6 Change B:增加响应适配层(items→tasks, 计算 hasMore, 透传 performance) export async function fetchTasks(params: FetchTasksParams = {}): Promise<{ tasks: Task[] performance: PerformanceData total: number hasMore: boolean }> { const page = params.page ?? 1 const pageSize = params.pageSize ?? 20 // 构建查询参数,过滤掉 undefined 值(避免序列化为 "undefined" 字符串) const query: Record = { page_size: pageSize, page } if (params.status) query.status = params.status const res = await request({ url: '/api/xcx/tasks', method: 'GET', // 后端 FastAPI Query 参数为 snake_case(page_size),前端 camelCase 需转换 data: query, needAuth: true, }) // 后端返回 { items, total, page, pageSize, performance },适配为前端期望的 { tasks, hasMore, ... } return { tasks: res.items ?? [], performance: res.performance, total: res.total ?? 0, hasMore: (page * pageSize) < (res.total ?? 0), } } /** 获取任务详情 */ export async function fetchTaskDetail(taskId: string): Promise { return request({ url: `/api/xcx/tasks/${taskId}`, method: 'GET', needAuth: true, }) } /** 按 member_id 查询最高优先级 active 任务详情 */ export async function fetchTaskByMember(memberId: string): Promise { return request({ url: `/api/xcx/tasks/by-member/${memberId}`, method: 'GET', needAuth: true, }) } /** 放弃任务 */ export async function abandonTask(taskId: string, reason: string): Promise { await request({ url: `/api/xcx/tasks/${taskId}/abandon`, method: 'POST', data: { reason }, needAuth: true, }) } /** 取消放弃(恢复任务) */ export async function restoreTask(taskId: string): Promise { await request({ url: `/api/xcx/tasks/${taskId}/restore`, method: 'POST', needAuth: true, }) } /** 置顶任务 */ export async function pinTask(taskId: string): Promise<{ isPinned: boolean }> { return request({ url: `/api/xcx/tasks/${taskId}/pin`, method: 'POST', needAuth: true, }) } /** 取消置顶任务 */ export async function unpinTask(taskId: string): Promise<{ isPinned: boolean }> { return request({ url: `/api/xcx/tasks/${taskId}/unpin`, method: 'POST', needAuth: true, }) } // ============================================ // 备注模块 // ============================================ /** 获取备注列表 */ export async function fetchNotes(params: { page?: number; pageSize?: number } = {}): Promise<{ notes: Note[] total: number hasMore: boolean }> { return request({ url: '/api/xcx/notes', method: 'GET', data: params, needAuth: true }) } /** 新增备注 */ export async function createNote(data: { targetType?: string targetId: number content: string taskId?: number ratingServiceWillingness?: number ratingRevisitLikelihood?: number score?: number }): Promise { return request({ url: '/api/xcx/notes', method: 'POST', data: { targetType: 'member', ...data }, needAuth: true }) } /** 删除备注 */ export async function deleteNote(noteId: number): Promise { await request({ url: `/api/xcx/notes/${noteId}`, method: 'DELETE', needAuth: true }) } // ============================================ // 绩效模块 // ============================================ /** 绩效概览 */ export async function fetchPerformanceOverview(params: { year: number month: number }): Promise { return request({ url: '/api/xcx/performance', method: 'GET', data: params, needAuth: true }) } /** 绩效明细(按月)— 对齐后端 PerformanceRecordsResponse */ export async function fetchPerformanceRecords(params: { year: number month: number page?: number pageSize?: number /** 目标助教 ID:从 coach-detail 跳入时传入,要求调用者具备 view_board_coach 权限 */ coachId?: number }): Promise<{ summary: { totalCount: number; totalHours: number; totalHoursRaw: number; totalIncome: number } dateGroups: Array<{ date: string totalHours: string totalIncome: string records: Array<{ customerName: string memberId?: number avatarChar?: string timeRange: string hours: string courseType: string location: string income: string }> }> hasMore: boolean }> { // 后端 FastAPI Query 参数为 snake_case,前端 camelCase 需手动映射 const query: Record = { year: params.year, month: params.month, page: params.page ?? 1, page_size: params.pageSize ?? 20, } if (params.coachId !== undefined && params.coachId !== null) { query.coach_id = params.coachId } return request({ url: '/api/xcx/performance/records', method: 'GET', data: query, needAuth: true, }) } // ============================================ // 客户模块 // ============================================ /** 客户详情 */ export async function fetchCustomerDetail(customerId: string): Promise { return request({ url: `/api/xcx/customers/${customerId}`, method: 'GET', needAuth: true, }) } /** 客户服务记录(对齐后端 CustomerRecordsResponse) */ export async function fetchCustomerRecords(params: { customerId: string year?: number month?: number table?: string }): Promise<{ customerName: string customerPhone: string customerPhoneFull: string relationIndex: string totalServiceCount: number monthCount: number monthHours: number monthIncome: number records: any[] hasMore: boolean }> { const { customerId, ...rest } = params return request({ url: `/api/xcx/customers/${customerId}/records`, method: 'GET', data: rest, needAuth: true, }) } /** 客户消费记录(CUST-3,按月) */ export async function fetchCustomerConsumptionRecords(params: { customerId: string year: number month: number }): Promise { const { customerId, ...rest } = params return request({ url: `/api/xcx/customers/${customerId}/consumption-records`, method: 'GET', data: rest, needAuth: true, }) } // ============================================ // 看板模块 // ============================================ /** 助教看板 */ export async function fetchBoardCoaches(params: { skill?: string sort?: string time?: string page?: number pageSize?: number } = {}): Promise<{ items: CoachCard[]; total: number; page: number; pageSize: number }> { const data = await request({ url: '/api/xcx/board/coaches', method: 'GET', data: params, needAuth: true, }) return data } /** 客户看板 */ export async function fetchBoardCustomers(params: { dimension?: string project?: string page?: number pageSize?: number } = {}): Promise<{ items: CustomerCard[]; total: number; page: number; pageSize: number }> { const data = await request({ url: '/api/xcx/board/customers', method: 'GET', data: params, needAuth: true, }) return data } // CHANGE 2026-03-19 | RNS1.3 T14: 扩展参数为 time/area/compare /** 财务看板 */ export async function fetchBoardFinance(params: { time?: string area?: string compare?: number } = {}): Promise { return request({ url: '/api/xcx/board/finance', method: 'GET', data: params, needAuth: true, }) } // ============================================ // 助教模块 // ============================================ /** 助教 banner 轻量信息(仅 name/level/storeName)— 比 fetchCoachDetail 快一个数量级 */ export async function fetchCoachBanner(coachId: string): Promise<{ id: number name: string level: string storeName: string } | null> { return request({ url: `/api/xcx/coaches/${coachId}/banner`, method: 'GET', needAuth: true, }) } /** 助教详情 */ export async function fetchCoachDetail(coachId: string): Promise { return request({ url: `/api/xcx/coaches/${coachId}`, method: 'GET', needAuth: true, }) } // ============================================ // 对话模块 // ============================================ /** 对话历史列表 */ export async function fetchChatHistory(params: { page?: number pageSize?: number } = {}): Promise<{ items: any[]; total: number; page: number; pageSize: number }> { return request({ url: '/api/xcx/chat/history', method: 'GET', data: params, needAuth: true, }) } /** 对话消息列表(通过 chatId) */ export async function fetchChatMessages(chatId: string, params: { page?: number pageSize?: number } = {}): Promise<{ chatId: number; items: any[]; total: number; page: number; pageSize: number }> { return request({ url: `/api/xcx/chat/${chatId}/messages`, method: 'GET', data: params, needAuth: true, }) } /** 对话消息列表(通过上下文类型和 ID,自动查找/创建对话) */ export async function fetchChatMessagesByContext( contextType: string, contextId: string, params: { page?: number; pageSize?: number } = {}, ): Promise<{ chatId: number; items: any[]; total: number; page: number; pageSize: number }> { return request({ url: '/api/xcx/chat/messages', method: 'GET', data: { contextType, contextId, ...params }, needAuth: true, }) } /** 发送消息 */ export async function sendChatMessage(chatId: string, content: string): Promise<{ userMessage: { id: number; content: string; createdAt: string } aiReply: { id: number; content: string; createdAt: string } }> { return request({ url: `/api/xcx/chat/${chatId}/messages`, method: 'POST', data: { content }, needAuth: true, }) } // ============================================ // 配置模块 // ============================================ /** AI 缓存查询(Phase 2.5) */ export async function fetchAICache(cacheType: string, targetId: string): Promise<{ result_json: Record | null; score: number | null; } | null> { try { const data = await request({ url: `/api/ai/cache/${cacheType}`, method: 'GET', data: { target_id: targetId }, needAuth: true, }) if (!data) return null const d = data as any return { result_json: d.result_json ?? null, score: d.score ?? null } } catch { return null } } /** 项目类型筛选器列表(CONFIG-1) */ // CHANGE 2026-03-20 | R3 修复:value 改为数据库 category_code,fallback 与后端一致 export async function fetchSkillTypes(): Promise> { const data = await request({ url: '/api/xcx/config/skill-types', method: 'GET', needAuth: true, }) // 后端返回 [{key, label, emoji, cls}, ...],映射为前端 {value, text, icon} return (data as Array<{key: string; label: string; emoji: string; cls: string}>).map( (item: {key: string; label: string; emoji: string}) => ({ value: item.key, text: item.label, icon: item.emoji || undefined, }) ) }