Files
Neo-ZQYY/apps/miniprogram/miniprogram/services/api.ts
Neo caf179a5da feat: 2026-04-15~05-02 累积变更基线 — AI 重构 + Runtime Context + DWS 修复
涵盖(每条对应已存的审计记录):
- AI 模块拆分:apps/backend/app/ai/apps -> prompts/(8 个 APP + app2a 派生)
  audit: 2026-04-20__ai-module-complete.md
- admin-web AI 管理套件:AIDashboard / AIOperations / AIRunLogs / AITriggers / TriggerManager
  audit: 2026-04-21__admin-web-ai-management-suite.md
- App2 财务洞察 prompt v3 -> v5.1 + 小程序 AI 接入(chat / board-finance)
  audit: 2026-04-22__app2_prompt_v5_1_and_miniprogram_ai_insight.md
- App2 prewarm 全过滤器 + AI 触发器 cron reschedule
  audit: 2026-04-21__app2-finance-prewarm-all-filters.md
  migration: 20260420_ai_trigger_jobs_and_app2_prewarm.sql / 20260421_app2_prewarm_cron_reschedule.sql
- AppType 联合类型对齐 + adminAiAppTypes.test.ts
  audit: 2026-04-30__admin_web_ai_app_type_alignment.md
- DashScope tokens_used 提取修复
  audit: 2026-04-30__backend_dashscope_tokens_used_extraction.md
- App3 线索完整详情 prompt
  audit: 2026-05-01__backend_app3_full_detail_prompt.md
- Runtime Context 沙箱(5-1~5-2 主线):
  - 后端 schema/service + admin_runtime_context / xcx_runtime_clock 两个 router
  - admin-web RuntimeContext.tsx + miniprogram runtime-clock.ts
  - migration: 20260501__runtime_context_sandbox.sql
  - tools/db/verify_admin_web_sandbox.py + verify_sandbox_end_to_end.py
  - database/changes: 7 份 sandbox_* 验证报告
- 飞球 DWS 修复:finance_area_daily 区域汇总 + task_engine 调整
  + RLS 视图业务日上界(migration 20260502 + scripts/ops/gen_rls_business_date_migration.py)

合规:
- .gitignore 启用 tmp/ 排除
- 不入仓:apps/etl/connectors/feiqiu/.env(API_TOKEN secret,本地修改保留)

待验证清单:
- docs/audit/changes/2026-05-04__cumulative_baseline_pending_verification.md
  每个主题的功能完整性 / 上线验证几乎都未收口,按优先级 P0~P3 逐一处理
2026-05-04 02:30:19 +08:00

478 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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/SNOOKERAPI 响应映射从
// 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<ApiUserInfo> {
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<RuntimeClock> {
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<string, any> = { page_size: pageSize, page }
if (params.status) query.status = params.status
const res = await request({
url: '/api/xcx/tasks',
method: 'GET',
// 后端 FastAPI Query 参数为 snake_casepage_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<TaskDetail | null> {
return request({
url: `/api/xcx/tasks/${taskId}`,
method: 'GET',
needAuth: true,
})
}
/** 按 member_id 查询最高优先级 active 任务详情 */
export async function fetchTaskByMember(memberId: string): Promise<TaskDetail | null> {
return request({
url: `/api/xcx/tasks/by-member/${memberId}`,
method: 'GET',
needAuth: true,
})
}
/** 放弃任务 */
export async function abandonTask(taskId: string, reason: string): Promise<void> {
await request({
url: `/api/xcx/tasks/${taskId}/abandon`,
method: 'POST',
data: { reason },
needAuth: true,
})
}
/** 取消放弃(恢复任务) */
export async function restoreTask(taskId: string): Promise<void> {
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<any> {
return request({ url: '/api/xcx/notes', method: 'POST', data: { targetType: 'member', ...data }, needAuth: true })
}
/** 删除备注 */
export async function deleteNote(noteId: number): Promise<void> {
await request({ url: `/api/xcx/notes/${noteId}`, method: 'DELETE', needAuth: true })
}
// ============================================
// 绩效模块
// ============================================
/** 绩效概览 */
export async function fetchPerformanceOverview(params: {
year: number
month: number
}): Promise<PerformanceData> {
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<string, any> = {
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<CustomerDetail | null> {
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<any> {
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<BoardFinanceData> {
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<CoachCard | null> {
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<string, any> | 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_codefallback 与后端一致
export async function fetchSkillTypes(): Promise<Array<{ value: string; text: string; icon?: string }>> {
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,
})
)
}