f3353bbce5
Co-authored-by: Cursor <cursoragent@cursor.com>
211 lines
5.0 KiB
TypeScript
211 lines
5.0 KiB
TypeScript
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<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||
const headers: Record<string, string> = {
|
||
'Content-Type': 'application/json',
|
||
'X-Client-App': CLIENT_APP,
|
||
...(options.headers as Record<string, string>),
|
||
};
|
||
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<T> = {
|
||
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;
|
||
};
|
||
};
|