feat: chat integration, tenant admin spec, backend chat service, miniprogram updates, DEMO moved to tmp, XCX-TEST removed, migrations & docs

This commit is contained in:
Neo
2026-03-20 09:02:10 +08:00
parent 3d2e5f8165
commit beb88d5bea
388 changed files with 6436 additions and 25458 deletions

View File

@@ -0,0 +1,452 @@
/**
* 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: '打赏课' },
]
}