export const apiBase = '/api/v1'; export const CLIENT_APP = 'HQ_WEB'; export type HqProfile = { id: string; phone: string; name: string; adminRole: string; status: string; permissionKeys?: string[]; }; export function getToken() { return localStorage.getItem('accessToken'); } export function saveAuth(data: { accessToken: string; refreshToken?: string }) { localStorage.setItem('accessToken', data.accessToken); if (data.refreshToken) localStorage.setItem('refreshToken', data.refreshToken); } export function clearAuth() { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); } export async function request(path: string, options: RequestInit = {}): Promise { const headers: Record = { 'Content-Type': 'application/json', 'X-Client-App': CLIENT_APP, ...(options.headers as Record), }; const token = getToken(); if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(`${apiBase}${path}`, { ...options, headers }); const text = await res.text(); if (!text.trim()) { throw new Error(`接口空响应(HTTP ${res.status} ${res.statusText || ''})`); } let json: { code: number; message?: string; data?: T }; try { json = JSON.parse(text) as { code: number; message?: string; data?: T }; } catch { throw new Error(`接口返回非 JSON(HTTP ${res.status}): ${text.slice(0, 200)}`); } if (json.code === 401) { clearAuth(); window.location.href = '/login'; throw new Error('未登录'); } if (json.code !== 0) throw new Error(json.message || '请求失败'); return json.data as T; } export type Paginated = { items: T[]; total: number; page: number; pageSize: number; summary?: { count: number; redeemAmount?: number; payoutAmount?: number; orderAmount?: number; orderCommission?: number; redeemCommission?: number; wineryAmount?: number; totalAmount: number; }; }; export type DashboardStats = { usersTotal: number; guestUsers: number; verifiedUsers: number; mergedUsers: number; ordersToday: number; storesTotal: number; partnersTotal: number; redeemToday: number; deliveriesTotal: number; pendingPayouts?: number; /** 合伙人已确认、待总部审核打款 */ pendingBills?: number; pendingPartnerDraftBills?: number; openTickets?: number; pendingStoreWithdrawals?: number; overdueStoreWithdrawals?: number; ordersByStatus: Array<{ status: string; count: number }>; }; export type DashboardAnalytics = { summary: { users: number; orders: number; payingUsers: number; partners: number; stores: number; redeems: number; redeemAmount: number; }; byDate: Array<{ date: string; users: number; orders: number; partners: number; stores: number; redeems: number; redeemAmount: number; }>; byCity: Array<{ cityId: string; cityName: string; users: number; orders: number; partners: number; stores: number; redeems: number; redeemAmount: number; }>; byPromo: Array<{ promoCodeId: string | null; code: string; name: string; users: number; orders: number; }>; byPartner: Array<{ partnerAccountId: string; companyName: string; stores: number; redeems: number; redeemAmount: number; }>; }; export type SystemVersion = { id: string; gitTag: string | null; commitId: string; commitMessage: string; branch: string | null; deployedBy: string | null; deployedAt: string; }; export type DeployTriggerResult = { accepted: boolean; started?: boolean; message: string; }; export type AdminUserRow = { id: string; userNo: string; deviceKey: string | null; phone: string | null; phoneVerifiedAt: string | null; mergedIntoUserId: string | null; wxOpenId: string | null; wechatVerified: boolean; nickname: string | null; status: number; sourceType: string; sourceRefId: string | null; sourceLabel: string | null; createdAt: string; orderCount: number; }; export type AdminOrderItem = { productName: string; productSpec: string; productImage: string; unitPrice: number; quantity: number; }; export type AdminOrderRow = { id: string; orderNo: string; status: string; deliveryType: string; payAmount: number; productName?: string; productSpec?: string; quantity?: number; receiverName: string; receiverPhone: string; createdAt: string; cityId?: string; city?: { id: string; name: string; code: string }; fulfillmentWarehouseId?: string | null; fulfillmentWarehouse?: { id: string; name: string } | null; fulfillmentHold?: boolean; fulfillmentHoldReason?: string | null; orderType?: string; isProxyOrder?: boolean; proxyPartnerName?: string | null; proxyPartnerPhone?: string | null; user?: { id: string; userNo: string; phone: string | null; nickname: string | null }; delivery?: { provider: string; trackingNo: string | null; providerOrderNo: string | null; logisticsCompany?: string | null; manualQueryUrl?: string | null; }; };