涵盖(每条对应已存的审计记录): - 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 逐一处理
478 lines
13 KiB
TypeScript
478 lines
13 KiB
TypeScript
// 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<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_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<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_code,fallback 与后端一致
|
||
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,
|
||
})
|
||
)
|
||
}
|