453 lines
11 KiB
TypeScript
453 lines
11 KiB
TypeScript
/**
|
||
* 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<void> {
|
||
return new Promise(resolve => setTimeout(resolve, ms))
|
||
}
|
||
|
||
/**
|
||
* 是否使用真实 API(联调开关)
|
||
* 联调时改为 true,或按模块逐个开启
|
||
*/
|
||
const USE_REAL_API = false
|
||
|
||
// ============================================
|
||
// 认证模块(已对接真实 API)
|
||
// ============================================
|
||
|
||
/** 查询当前用户信息 */
|
||
export async function fetchMe(): Promise<ApiUserInfo> {
|
||
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<TaskDetail | null> {
|
||
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<void> {
|
||
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<void> {
|
||
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<void> {
|
||
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<PerformanceData> {
|
||
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<MockCustomerDetail | null> {
|
||
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<CoachCard[]> {
|
||
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<CustomerCard[]> {
|
||
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<BoardFinanceData> {
|
||
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<CoachCard | null> {
|
||
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<Array<{ value: string; text: string; icon?: string }>> {
|
||
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: '打赏课' },
|
||
]
|
||
}
|