Files
dukang/apps/admin-web/src/lib/api.ts
T
jacy fccfd7abbe feat(promo): HQ 推广码订单只计已完成并补扫码订单快链
列表与详情的订单数/转化率按 COMPLETED 实时统计;扫码、订单、事件 ID 可跳到对应页。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 12:55:19 +08:00

243 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { HqListColumnPrefsMap } from '@dukang/shared-types';
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[];
cityIds?: string[];
cityScoped?: boolean;
listColumnPrefs?: HqListColumnPrefsMap;
};
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(`接口返回非 JSONHTTP ${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;
pendingStoreOnboard?: number;
pendingStorePackageAudits?: number;
pendingStoreInfoChanges?: 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;
hqRemark: string | null;
status: number;
sourceType: string;
sourceRefId: string | null;
sourceLabel: string | null;
createdAt: string;
orderCount: number;
isTest?: boolean;
/** 好客权益·累计获得(含已使用,不含退款作废) */
benefitTotalAmount?: number;
/** 好客权益·已使用(已核销) */
benefitUsedAmount?: number;
/** 好客权益·剩余未使用 */
benefitBalance?: 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;
saleUnit?: string;
receiverName: string;
receiverPhone: string;
receiverProvince?: string;
receiverCity?: string;
receiverDistrict?: string;
receiverAddress?: string;
benefitAmount?: number;
benefitCoupon?: {
couponNo?: string;
totalAmount?: number;
usedAmount?: number;
balance?: number;
status?: string;
} | null;
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;
isTest?: boolean;
user?: { id: string; userNo: string; phone: string | null; nickname: string | null; hqRemark?: string | null };
delivery?: {
provider: string;
trackingNo: string | null;
providerOrderNo: string | null;
logisticsCompany?: string | null;
manualQueryUrl?: string | null;
/** 当次应付物流费(按瓶当量 × 承运商计价) */
logisticsFee?: number | null;
};
};