import type { CleaningSettlement, CleaningSettlementDetail, CleaningStatistics, CleaningTask, CleaningTaskEvent, CleaningTaskMember, ManagedUser, PageResult, PayoutStateFilter, StaffRole, SettlementStatus, TaskStatus, TransferMode, UserStatus, WechatTransferPreflight } from './types'; const API_BASE = (import.meta.env.VITE_ADMIN_API_BASE_URL || '/admin-api').replace(/\/$/, ''); export class ApiError extends Error { constructor( public readonly code: string, message = code, public readonly traceId = '' ) { super(message); } } export interface ApiSession { token: string; } async function request( session: ApiSession, path: string, options: RequestInit = {} ): Promise { if (!session.token.trim()) { throw new ApiError('AUTH_TOKEN_REQUIRED', '需要先填入后台访问令牌'); } const headers = new Headers(options.headers); headers.set('authorization', `Bearer ${session.token.trim()}`); if (options.body && !headers.has('content-type')) { headers.set('content-type', 'application/json'); } const response = await fetch(`${API_BASE}${path}`, { ...options, headers }); const payload = await response.json().catch(() => ({})); if (!response.ok || payload.code !== 0) { throw new ApiError( String(payload.code || `HTTP_${response.status}`), String(payload.message || '请求失败'), String(payload.traceId || '') ); } return payload.data as T; } export function listCleaningTasks( session: ApiSession, input: { page: number; pageSize: number; status?: TaskStatus; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/tasks?${params}`); } export function getCleaningStatistics( session: ApiSession, input: { from?: string; to?: string; storeId?: string; cleanerUserId?: string } = {} ) { const params = new URLSearchParams(); if (input.from) params.set('from', input.from); if (input.to) params.set('to', input.to); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); const query = params.toString(); return request(session, `/cleaning/statistics${query ? `?${query}` : ''}`); } export function listStaffUsers( session: ApiSession, input: { page: number; pageSize: number; role?: StaffRole; status?: UserStatus; search?: string; } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.role) params.set('role', input.role); if (input.status) params.set('status', input.status); if (input.search) params.set('search', input.search); return request<{ items: ManagedUser[]; total: number }>(session, `/users?${params}`); } export function createStaffUser( session: ApiSession, input: { nickname: string; phone: string; note?: string; roles: StaffRole[]; storeIds: string[] } ) { return request<{ userId: string }>(session, '/staff', { method: 'POST', body: JSON.stringify(input) }); } export function updateStaffUser( session: ApiSession, userId: string, input: Partial<{ nickname: string; phone: string; note: string; status: UserStatus; roles: StaffRole[]; storeIds: string[]; }> ) { return request<{ userId: string }>(session, `/users/${encodeURIComponent(userId)}`, { method: 'PATCH', body: JSON.stringify(input) }); } export function resetStaffSessions(session: ApiSession, userId: string) { return request<{ userId: string; revokedSessions: number }>( session, `/users/${encodeURIComponent(userId)}/reset-sessions`, { method: 'POST' } ); } export function assignCleaningTask( session: ApiSession, taskId: string, input: { cleanerUserId: string; note?: string } ) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/assign`, { method: 'POST', body: JSON.stringify(input) }); } export function completeCleaningTask(session: ApiSession, taskId: string, note?: string) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/complete`, { method: 'POST', body: JSON.stringify({ note }) }); } export function rejectCleaningTask(session: ApiSession, taskId: string, reason: string) { return request(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/reject`, { method: 'POST', body: JSON.stringify({ reason }) }); } export function listCleaningTaskMembers(session: ApiSession, taskId: string) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members` ); } export function listCleaningTaskEvents(session: ApiSession, taskId: string) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/events` ); } export function addCleaningTaskMember( session: ApiSession, taskId: string, input: { cleanerUserId: string; rewardCents: number; note?: string } ) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members`, { method: 'POST', body: JSON.stringify(input) } ); } export function removeCleaningTaskMember( session: ApiSession, taskId: string, cleanerUserId: string, note?: string ) { return request( session, `/cleaning/tasks/${encodeURIComponent(taskId)}/members/${encodeURIComponent(cleanerUserId)}/remove`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function reclaimCleaningTimeouts( session: ApiSession, input: { olderThanMinutes: number; limit: number } ) { return request<{ reclaimed: number; taskIds: string[] }>(session, '/cleaning/reclaim-timeouts', { method: 'POST', body: JSON.stringify(input) }); } export function listCleaningSettlements( session: ApiSession, input: { page: number; pageSize: number; status?: SettlementStatus; payoutState?: PayoutStateFilter; storeId?: string; cleanerUserId?: string; } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.status) params.set('status', input.status); if (input.payoutState) params.set('payoutState', input.payoutState); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/settlements?${params}`); } export function listCleaningSettlementCandidates( session: ApiSession, input: { page: number; pageSize: number; storeId?: string; cleanerUserId?: string } ) { const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) }); if (input.storeId) params.set('storeId', input.storeId); if (input.cleanerUserId) params.set('cleanerUserId', input.cleanerUserId); return request>(session, `/cleaning/settlement-candidates?${params}`); } export function generateCleaningSettlement( session: ApiSession, input: { cleanerUserId: string; storeId?: string; note?: string } ) { return request(session, '/cleaning/settlements', { method: 'POST', body: JSON.stringify(input) }); } export function getCleaningSettlementDetail(session: ApiSession, settlementId: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}` ); } export function confirmCleaningSettlement(session: ApiSession, settlementId: string, note?: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/confirm`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function executeWechatTransfer( session: ApiSession, settlementId: string, input: { mode: TransferMode; note?: string } ) { return request<{ settlement: CleaningSettlement; transferState: string; idempotent: boolean }>( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer`, { method: 'POST', body: JSON.stringify(input) } ); } export function preflightWechatTransfer(session: ApiSession, settlementId: string) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer/preflight` ); } export function syncWechatTransfer(session: ApiSession, settlementId: string, note?: string) { return request<{ settlement: CleaningSettlement; transferState: string; idempotent: boolean }>( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/wechat-transfer/sync`, { method: 'POST', body: JSON.stringify({ note }) } ); } export function recordPayoutFailure( session: ApiSession, settlementId: string, input: { error: string; payoutChannel?: string; payoutReference?: string; note?: string } ) { return request( session, `/cleaning/settlements/${encodeURIComponent(settlementId)}/payout-failure`, { method: 'POST', body: JSON.stringify(input) } ); }