917 lines
31 KiB
TypeScript
917 lines
31 KiB
TypeScript
import type {
|
|
Advertisement,
|
|
AdvertisementInput,
|
|
AdminIdentity,
|
|
AdminSessionPayload,
|
|
AdminAccess,
|
|
AuditLog,
|
|
CleaningSettlement,
|
|
CleaningSettlementDetail,
|
|
CleaningStatistics,
|
|
CleaningTask,
|
|
CleaningTemplate,
|
|
CleaningTemplateScope,
|
|
CleaningExemptPolicy,
|
|
CleaningTaskEvent,
|
|
CleaningTaskMember,
|
|
CleaningTaskSubmission,
|
|
DecorationComponent,
|
|
DecorationVersion,
|
|
FranchiseApplication,
|
|
FranchiseApplicationDetail,
|
|
FranchiseFollowUpType,
|
|
FranchiseStatus,
|
|
DeviceTopology,
|
|
DeviceType,
|
|
BusinessStatistics,
|
|
ManagedRoom,
|
|
MemberCard,
|
|
MemberDetail,
|
|
ManagedOrder,
|
|
ManagedStore,
|
|
ManagedUser,
|
|
MediaAsset,
|
|
PageResult,
|
|
OrderAction,
|
|
OrderHistoryItem,
|
|
OrderStatus,
|
|
PaymentAuthorizationStatus,
|
|
PlatformApplication,
|
|
ProfitSharingSnapshot,
|
|
PayoutStateFilter,
|
|
StaffRole,
|
|
SettlementStatus,
|
|
SystemOverview,
|
|
TaskStatus,
|
|
TransferMode,
|
|
RoomConfigurationStatus,
|
|
RoomInput,
|
|
RoomOperationalStatus,
|
|
StoreInput,
|
|
TenantApplicationConfig,
|
|
ThirdPartyMode,
|
|
ThirdPartyProvider,
|
|
ThirdPartyRecords,
|
|
ThirdPartySetup,
|
|
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;
|
|
}
|
|
|
|
const ACCESS_TOKEN_KEY = 'qipai.admin.token';
|
|
const REFRESH_TOKEN_KEY = 'qipai.admin.refreshToken';
|
|
let refreshInFlight: Promise<AdminSessionPayload> | null = null;
|
|
|
|
export async function adminPasswordLogin(input: {
|
|
tenantCode: string; loginName: string; password: string;
|
|
}) {
|
|
return publicRequest<AdminSessionPayload>('/auth/login', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function getAdminMe(session: ApiSession) {
|
|
return request<{ user: AdminIdentity; access: AdminAccess }>(session, '/auth/me');
|
|
}
|
|
|
|
export function logoutAdmin(session: ApiSession) {
|
|
return request<{ revoked: boolean }>(session, '/auth/logout', { method: 'POST' });
|
|
}
|
|
|
|
export function setAdminCredential(session: ApiSession, userId: string, input: {
|
|
loginName: string; password: string;
|
|
}) {
|
|
return request<{ userId: string; configured: boolean }>(session,
|
|
`/auth/credentials/${encodeURIComponent(userId)}`,
|
|
{ method: 'PUT', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function listManagedStores(session: ApiSession) {
|
|
return request<ManagedStore[]>(session, '/stores');
|
|
}
|
|
|
|
export function createManagedStore(session: ApiSession, input: StoreInput) {
|
|
return request<{ storeId: string }>(session, '/stores', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function updateManagedStore(session: ApiSession, storeId: string, input: StoreInput) {
|
|
return request<{ storeId: string }>(session, `/stores/${storeId}`, {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function archiveManagedStore(session: ApiSession, storeId: string) {
|
|
return request<{ storeId: string; archived: boolean }>(session, `/stores/${storeId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
}
|
|
|
|
export function listManagedRooms(session: ApiSession, storeId: string) {
|
|
return request<ManagedRoom[]>(session, `/stores/${storeId}/rooms`);
|
|
}
|
|
|
|
export function createManagedRoom(session: ApiSession, input: RoomInput) {
|
|
return request<{ roomId: string }>(session, '/rooms', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function updateManagedRoom(session: ApiSession, roomId: string, input: RoomInput) {
|
|
return request<{ roomId: string }>(session, `/rooms/${roomId}`, {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function updateManagedRoomStatus(
|
|
session: ApiSession,
|
|
roomId: string,
|
|
input: {
|
|
storeId: string;
|
|
configurationStatus?: RoomConfigurationStatus;
|
|
operationalStatus?: RoomOperationalStatus;
|
|
reason: string;
|
|
}
|
|
) {
|
|
return request<{ roomId: string }>(session, `/rooms/${roomId}/status`, {
|
|
method: 'PATCH', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function archiveManagedRoom(session: ApiSession, roomId: string, storeId: string) {
|
|
return request<{ roomId: string; archived: boolean }>(
|
|
session, `/rooms/${roomId}?${new URLSearchParams({ storeId })}`, { method: 'DELETE' }
|
|
);
|
|
}
|
|
|
|
export function addManagedRoomDisabledPeriod(
|
|
session: ApiSession,
|
|
roomId: string,
|
|
input: { storeId: string; startsAt: string; endsAt: string; reason: string }
|
|
) {
|
|
return request<{ disabledPeriodId: string }>(session, `/rooms/${roomId}/disabled-periods`, {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function listManagedOrders(
|
|
session: ApiSession,
|
|
input: { page: number; pageSize: number; status?: OrderStatus; storeId?: 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);
|
|
return request<PageResult<ManagedOrder>>(session, `/orders?${params}`);
|
|
}
|
|
|
|
export function getManagedOrder(session: ApiSession, orderId: string) {
|
|
return request<ManagedOrder>(session, `/orders/${orderId}`);
|
|
}
|
|
|
|
export function listManagedOrderHistory(session: ApiSession, orderId: string) {
|
|
return request<OrderHistoryItem[]>(session, `/orders/${orderId}/history`);
|
|
}
|
|
|
|
export function executeManagedOrderAction(
|
|
session: ApiSession, orderId: string, action: OrderAction, reason: string
|
|
) {
|
|
return request<{ orderId: string; status: OrderStatus }>(session, `/orders/${orderId}/actions`, {
|
|
method: 'POST', body: JSON.stringify({ action, reason })
|
|
});
|
|
}
|
|
|
|
export function addManagedOrderNote(session: ApiSession, orderId: string, note: string) {
|
|
return request<{ orderId: string }>(session, `/orders/${orderId}/note`, {
|
|
method: 'POST', body: JSON.stringify({ note })
|
|
});
|
|
}
|
|
|
|
export function adjustManagedOrderTime(
|
|
session: ApiSession, orderId: string,
|
|
input: { startAt?: string; endAt?: string; reason: string }
|
|
) {
|
|
return request<{ orderId: string }>(session, `/orders/${orderId}/adjust-time`, {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function renewManagedOrder(
|
|
session: ApiSession, orderId: string,
|
|
input: { endAt: string; pricingPolicy: 'CURRENT' | 'LOCKED'; reason: string }
|
|
) {
|
|
return request<{ orderId: string }>(session, `/orders/${orderId}/renew`, {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function changeManagedOrderRoom(
|
|
session: ApiSession, orderId: string, input: { roomId: string; reason: string }
|
|
) {
|
|
return request<{ orderId: string }>(session, `/orders/${orderId}/change-room`, {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function getProfitSharingSnapshot(session: ApiSession, storeId?: string) {
|
|
const query = storeId ? `?${new URLSearchParams({ storeId })}` : '';
|
|
return request<ProfitSharingSnapshot>(session, `/pay/profit-shares${query}`);
|
|
}
|
|
|
|
export function saveCollectionAccount(session: ApiSession, input: {
|
|
storeId: string | null; merchantId: string; credentialRef: string;
|
|
authorizationStatus: PaymentAuthorizationStatus; profitSharingEnabled: boolean; enabled: boolean;
|
|
}) {
|
|
return request<{ collectionAccountId: string }>(session, '/pay/collection-account', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function saveProfitShareReceiver(session: ApiSession, input: {
|
|
collectionAccountId: string; receiverType: 'MERCHANT_ID' | 'PERSONAL_OPENID';
|
|
receiverAccount: string; receiverCredentialRef: string; relationType: string; name: string;
|
|
authorizationStatus: PaymentAuthorizationStatus; enabled: boolean;
|
|
}) {
|
|
return request<{ receiverId: string }>(session, '/pay/profit-share-receiver', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function saveProfitSharePolicy(session: ApiSession, input: {
|
|
collectionAccountId: string; storeId: string | null; receiverId: string;
|
|
percentageBps: number; enabled: boolean;
|
|
}) {
|
|
return request<{ policyId: string }>(session, '/pay/profit-share-policy', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function executeProfitSharing(
|
|
session: ApiSession, input: { paymentId: string; clientRequestId: string; mode: 'API' | 'MOCK' }
|
|
) {
|
|
return request<{ shares: unknown[]; idempotent: boolean }>(session, '/pay/profit-shares', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function createWechatRefund(session: ApiSession, input: {
|
|
paymentId: string; amountCents: number; reason: string; clientRequestId: string;
|
|
}) {
|
|
return request<{ refundId: string; refundNo: string; status: string; refundableCents: number }>(
|
|
session, '/pay/refund', { method: 'POST', body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function requestWechatReconciliation(session: ApiSession, input: {
|
|
storeId: string; billDate: string; billType: 'ALL' | 'SUCCESS' | 'REFUND';
|
|
}) {
|
|
return request<{ id: string; status: string; downloadUrl: string; idempotent: boolean }>(
|
|
session, '/pay/reconciliation', { method: 'POST', body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function listThirdPartyRecords(session: ApiSession, input: {
|
|
provider?: ThirdPartyProvider; status?: string; storeId?: string;
|
|
}) {
|
|
const params = new URLSearchParams();
|
|
if (input.provider) params.set('provider', input.provider);
|
|
if (input.status) params.set('status', input.status);
|
|
if (input.storeId) params.set('storeId', input.storeId);
|
|
const query = params.toString();
|
|
return request<ThirdPartyRecords>(session, `/third-party/records${query ? `?${query}` : ''}`);
|
|
}
|
|
|
|
export function getThirdPartySetup(session: ApiSession, provider?: ThirdPartyProvider) {
|
|
const query = provider ? `?${new URLSearchParams({ provider })}` : '';
|
|
return request<ThirdPartySetup>(session, `/third-party/setup${query}`);
|
|
}
|
|
|
|
export function saveThirdPartyConfig(session: ApiSession, input: {
|
|
provider: ThirdPartyProvider; storeId: string | null; mode: ThirdPartyMode;
|
|
enabled: boolean; credentialRef: string; settings: Record<string, unknown>;
|
|
}) {
|
|
return request<{ configId: string; created: boolean }>(session, '/third-party/config', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function saveThirdPartyMapping(session: ApiSession, input: {
|
|
provider: ThirdPartyProvider; resourceType: 'STORE' | 'ROOM';
|
|
externalRef: string; localResourceId: string;
|
|
}) {
|
|
return request<{ mapped: boolean }>(session, '/third-party/mappings', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function redeemGroupVoucher(session: ApiSession, input: {
|
|
provider: ThirdPartyProvider; voucherCode: string; orderId: string; clientRequestId: string;
|
|
}) {
|
|
return request<{ redemptionId: string; status: string; voucherMasked: string }>(
|
|
session, '/group-vouchers/redeem', { method: 'POST', body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function redeemGroupVoucherManually(session: ApiSession, input: {
|
|
provider: ThirdPartyProvider; voucherCode: string; orderId: string;
|
|
amountCents: number; note: string; clientRequestId: string;
|
|
}) {
|
|
return request<{ redemptionId: string; status: string; voucherMasked: string }>(
|
|
session, '/group-vouchers/redeem-manual', { method: 'POST', body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function getBusinessStatistics(
|
|
session: ApiSession,
|
|
input: { storeId: string; from: string; to: string }
|
|
) {
|
|
const params = new URLSearchParams(input);
|
|
return request<BusinessStatistics>(session, `/statistics?${params}`);
|
|
}
|
|
|
|
async function request<T>(
|
|
session: ApiSession,
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
if (!session.token.trim()) {
|
|
throw new ApiError('AUTH_TOKEN_REQUIRED', '需要先填入后台访问令牌');
|
|
}
|
|
let response = await authorizedFetch(path, options, session.token.trim());
|
|
if (response.status === 401 && localStorage.getItem(REFRESH_TOKEN_KEY)) {
|
|
try {
|
|
const refreshed = await refreshAccessToken();
|
|
response = await authorizedFetch(path, options, refreshed.accessToken);
|
|
} catch {
|
|
clearStoredSession();
|
|
window.dispatchEvent(new Event('qipai:admin-unauthorized'));
|
|
}
|
|
}
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok || payload.code !== 0) {
|
|
if (response.status === 401) {
|
|
clearStoredSession();
|
|
window.dispatchEvent(new Event('qipai:admin-unauthorized'));
|
|
}
|
|
throw new ApiError(
|
|
String(payload.code || `HTTP_${response.status}`),
|
|
String(payload.message || '请求失败'),
|
|
String(payload.traceId || '')
|
|
);
|
|
}
|
|
return payload.data as T;
|
|
}
|
|
|
|
async function publicRequest<T>(path: string, options: RequestInit): Promise<T> {
|
|
const headers = new Headers(options.headers);
|
|
headers.set('content-type', 'application/json');
|
|
headers.set('x-request-id', requestId());
|
|
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;
|
|
}
|
|
|
|
async function authorizedFetch(path: string, options: RequestInit, token: string) {
|
|
const headers = new Headers(options.headers);
|
|
headers.set('authorization', `Bearer ${token}`);
|
|
headers.set('x-request-id', requestId());
|
|
if (options.body && !headers.has('content-type')) headers.set('content-type', 'application/json');
|
|
return fetch(`${API_BASE}${path}`, { ...options, headers });
|
|
}
|
|
|
|
async function refreshAccessToken() {
|
|
if (!refreshInFlight) {
|
|
const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY) || '';
|
|
refreshInFlight = publicRequest<AdminSessionPayload>('/auth/refresh', {
|
|
method: 'POST', body: JSON.stringify({ refreshToken })
|
|
}).then((payload) => {
|
|
storeAdminSession(payload);
|
|
window.dispatchEvent(new CustomEvent('qipai:admin-refreshed', { detail: payload }));
|
|
return payload;
|
|
}).finally(() => { refreshInFlight = null; });
|
|
}
|
|
return refreshInFlight;
|
|
}
|
|
|
|
export function storeAdminSession(payload: AdminSessionPayload) {
|
|
localStorage.setItem(ACCESS_TOKEN_KEY, payload.accessToken);
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, payload.refreshToken);
|
|
}
|
|
|
|
export function clearStoredSession() {
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
}
|
|
|
|
function requestId() {
|
|
return globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
}
|
|
|
|
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<PageResult<CleaningTask>>(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<CleaningStatistics>(session, `/cleaning/statistics${query ? `?${query}` : ''}`);
|
|
}
|
|
|
|
export function listCleaningTemplates(session: ApiSession) {
|
|
return request<CleaningTemplate[]>(session, '/cleaning/templates');
|
|
}
|
|
|
|
export function upsertCleaningTemplate(
|
|
session: ApiSession,
|
|
input: {
|
|
scopeType: CleaningTemplateScope;
|
|
scopeId?: string;
|
|
name: string;
|
|
requirement: string;
|
|
photoRequired: boolean;
|
|
minPhotoCount: number;
|
|
maxPhotoCount: number;
|
|
exemptPolicy: CleaningExemptPolicy;
|
|
status: 'ACTIVE' | 'DISABLED';
|
|
}
|
|
) {
|
|
return request<CleaningTemplate>(session, '/cleaning/templates', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
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),
|
|
userType: 'STAFF'
|
|
});
|
|
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 listMembers(
|
|
session: ApiSession,
|
|
input: { page: number; pageSize: number; status?: UserStatus; search?: string }
|
|
) {
|
|
const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) });
|
|
if (input.status) params.set('status', input.status);
|
|
if (input.search) params.set('search', input.search);
|
|
return request<{ items: MemberCard[]; total: number }>(session, `/members?${params}`);
|
|
}
|
|
|
|
export function getMember(session: ApiSession, memberId: string) {
|
|
return request<MemberDetail>(session, `/members/${encodeURIComponent(memberId)}`);
|
|
}
|
|
|
|
export function getDeviceTopology(session: ApiSession, storeId: string) {
|
|
return request<DeviceTopology>(session, `/device-topology?${new URLSearchParams({ storeId })}`);
|
|
}
|
|
|
|
export function createDeviceAsset(session: ApiSession, input: {
|
|
storeId: string; roomId: string | null; deviceId: string; imei: string; iccid: string | null;
|
|
deviceType: DeviceType; model: string; firmwareVersion: string;
|
|
signalStrength: number | null; capabilities: string[];
|
|
}) {
|
|
return request<{ assetId: string }>(session, '/devices', { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function bindDeviceChannel(session: ApiSession, input: {
|
|
assetId: string; storeId: string; roomId: string; channelCode: string; purpose: string;
|
|
}) {
|
|
return request<{ assetId: string; targetKey: string }>(session, '/device-channels', { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function bindSubDevice(session: ApiSession, input: {
|
|
parentAssetId: string; childAssetId: string; storeId: string; roomId: string;
|
|
subId: string; subtype: string;
|
|
}) {
|
|
return request<{ childAssetId: string; parentAssetId: string }>(session, '/device-links', { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function addDeviceMaintenance(session: ApiSession, assetId: string, input: {
|
|
storeId: string; roomId: string | null; recordType: 'INSPECTION' | 'REPAIR' | 'REPLACEMENT';
|
|
status: 'OPEN' | 'RESOLVED'; description: string;
|
|
}) {
|
|
return request<{ maintenanceId: string }>(session, `/devices/${encodeURIComponent(assetId)}/maintenance`, { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function controlDevice(session: ApiSession, action: 'door' | 'power' | 'socket/switch', input: Record<string, unknown>) {
|
|
return request<{ commandId?: string; status?: string }>(session, `/device-control/${action}`, { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function listPlatformApplications(session: ApiSession) {
|
|
return request<PlatformApplication[]>(session, '/platform-apps');
|
|
}
|
|
|
|
export function updatePlatformApplicationConfig(session: ApiSession, platformAppId: string, input: TenantApplicationConfig) {
|
|
return request<{ platformAppId: string; updated: boolean }>(session, `/platform-apps/${encodeURIComponent(platformAppId)}/config`, { method: 'PUT', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function bindPlatformApplication(session: ApiSession, input: {
|
|
appId: string; appName: string; appStatus: 'ACTIVE' | 'DISABLED'; config: TenantApplicationConfig;
|
|
}) {
|
|
return request<{ platformAppId: string; bound: boolean }>(session, '/platform-apps/bind', { method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function listMediaAssets(session: ApiSession, storeId?: string) {
|
|
const query = storeId ? `?${new URLSearchParams({ storeId })}` : '';
|
|
return request<MediaAsset[]>(session, `/media/images${query}`);
|
|
}
|
|
|
|
export async function uploadMediaImage(session: ApiSession, file: File, storeId?: string) {
|
|
const headers = new Headers({
|
|
'content-type': 'application/octet-stream',
|
|
'x-image-content-type': file.type,
|
|
'x-file-name': file.name.replace(/[^\x20-\x7e]/g, '_') || 'image'
|
|
});
|
|
if (storeId) headers.set('x-store-id', storeId);
|
|
return request<{ assetId: string; url: string }>(session, '/media/images', {
|
|
method: 'POST', headers, body: await file.arrayBuffer()
|
|
});
|
|
}
|
|
|
|
export function listDecorations(session: ApiSession, storeId: string) {
|
|
return request<DecorationVersion[]>(
|
|
session, `/decorations?${new URLSearchParams({ storeId })}`
|
|
);
|
|
}
|
|
|
|
export function saveDecoration(session: ApiSession, input: {
|
|
storeId: string; templateCode: string; schemaVersion: number;
|
|
content: { components: DecorationComponent[] };
|
|
}) {
|
|
return request<{ decorationId: string; version: number }>(session, '/decorations', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function publishDecoration(session: ApiSession, decorationId: string, storeId: string) {
|
|
return request<{ decorationId: string; published: boolean }>(
|
|
session,
|
|
`/decorations/${encodeURIComponent(decorationId)}/publish?${new URLSearchParams({ storeId })}`,
|
|
{ method: 'POST' }
|
|
);
|
|
}
|
|
|
|
export function listAdvertisements(session: ApiSession) {
|
|
return request<Advertisement[]>(session, '/advertisements');
|
|
}
|
|
|
|
export function saveAdvertisement(session: ApiSession, input: AdvertisementInput) {
|
|
return request<{ advertisementId: string }>(session, '/advertisements', {
|
|
method: 'POST', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function updateAdvertisement(
|
|
session: ApiSession, advertisementId: string, input: AdvertisementInput
|
|
) {
|
|
return request<{ advertisementId: string }>(
|
|
session, `/advertisements/${encodeURIComponent(advertisementId)}`,
|
|
{ method: 'PUT', body: JSON.stringify(input) }
|
|
);
|
|
}
|
|
|
|
export function listFranchiseApplications(session: ApiSession, input: {
|
|
page: number; pageSize: number; status?: FranchiseStatus; assigneeUserId?: string; search?: string;
|
|
}) {
|
|
const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) });
|
|
if (input.status) params.set('status', input.status);
|
|
if (input.assigneeUserId) params.set('assigneeUserId', input.assigneeUserId);
|
|
if (input.search) params.set('search', input.search);
|
|
return request<PageResult<FranchiseApplication>>(session, `/franchise-applications?${params}`);
|
|
}
|
|
|
|
export function getFranchiseApplication(session: ApiSession, applicationId: string) {
|
|
return request<FranchiseApplicationDetail>(session, `/franchise-applications/${encodeURIComponent(applicationId)}`);
|
|
}
|
|
|
|
export function assignFranchiseApplication(session: ApiSession, applicationId: string, assigneeUserId: string | null) {
|
|
return request<{ applicationId: string; assigneeUserId: string | null }>(session,
|
|
`/franchise-applications/${encodeURIComponent(applicationId)}/assignee`,
|
|
{ method: 'PATCH', body: JSON.stringify({ assigneeUserId }) });
|
|
}
|
|
|
|
export function addFranchiseFollowUp(session: ApiSession, applicationId: string, input: {
|
|
followUpType: Exclude<FranchiseFollowUpType, 'ASSIGNMENT' | 'STATUS'>;
|
|
note: string; nextFollowUpAt: string | null; status?: FranchiseStatus;
|
|
}) {
|
|
return request<{ applicationId: string; followUpId: string; status: FranchiseStatus }>(session,
|
|
`/franchise-applications/${encodeURIComponent(applicationId)}/follow-ups`,
|
|
{ method: 'POST', body: JSON.stringify(input) });
|
|
}
|
|
|
|
export function listAuditLogs(session: ApiSession, input: {
|
|
page: number; pageSize: number; action?: string; resourceType?: string; search?: string;
|
|
from?: string; to?: string;
|
|
}) {
|
|
const params = new URLSearchParams({ page: String(input.page), pageSize: String(input.pageSize) });
|
|
if (input.action) params.set('action', input.action);
|
|
if (input.resourceType) params.set('resourceType', input.resourceType);
|
|
if (input.search) params.set('search', input.search);
|
|
if (input.from) params.set('from', input.from);
|
|
if (input.to) params.set('to', input.to);
|
|
return request<PageResult<AuditLog>>(session, `/audit-logs?${params}`);
|
|
}
|
|
|
|
export function getSystemOverview(session: ApiSession) {
|
|
return request<SystemOverview>(session, '/system/overview');
|
|
}
|
|
|
|
export function updateTenantSystemConfig(session: ApiSession, input: {
|
|
name: string; timezone: string; status?: 'ACTIVE' | 'DISABLED';
|
|
}) {
|
|
return request<{ tenantId: string; updated: boolean }>(session, '/system/tenant', {
|
|
method: 'PUT', body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
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<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/assign`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function completeCleaningTask(session: ApiSession, taskId: string, note?: string) {
|
|
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/complete`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ note })
|
|
});
|
|
}
|
|
|
|
export function rejectCleaningTask(session: ApiSession, taskId: string, reason: string) {
|
|
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/reject`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ reason })
|
|
});
|
|
}
|
|
|
|
export function exemptCleaningTask(session: ApiSession, taskId: string, note?: string) {
|
|
return request<CleaningTask>(session, `/cleaning/tasks/${encodeURIComponent(taskId)}/exempt`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ note })
|
|
});
|
|
}
|
|
|
|
export function listCleaningTaskMembers(session: ApiSession, taskId: string) {
|
|
return request<CleaningTaskMember[]>(
|
|
session,
|
|
`/cleaning/tasks/${encodeURIComponent(taskId)}/members`
|
|
);
|
|
}
|
|
|
|
export function listCleaningTaskEvents(session: ApiSession, taskId: string) {
|
|
return request<CleaningTaskEvent[]>(
|
|
session,
|
|
`/cleaning/tasks/${encodeURIComponent(taskId)}/events`
|
|
);
|
|
}
|
|
|
|
export function listCleaningTaskSubmissions(session: ApiSession, taskId: string) {
|
|
return request<CleaningTaskSubmission[]>(
|
|
session,
|
|
`/cleaning/tasks/${encodeURIComponent(taskId)}/submissions`
|
|
);
|
|
}
|
|
|
|
export function addCleaningTaskMember(
|
|
session: ApiSession,
|
|
taskId: string,
|
|
input: { cleanerUserId: string; rewardCents: number; note?: string }
|
|
) {
|
|
return request<CleaningTaskMember[]>(
|
|
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<CleaningTaskMember[]>(
|
|
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<PageResult<CleaningSettlement>>(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<PageResult<CleaningTask>>(session, `/cleaning/settlement-candidates?${params}`);
|
|
}
|
|
|
|
export function generateCleaningSettlement(
|
|
session: ApiSession,
|
|
input: { cleanerUserId: string; storeId?: string; note?: string }
|
|
) {
|
|
return request<CleaningSettlement>(session, '/cleaning/settlements', {
|
|
method: 'POST',
|
|
body: JSON.stringify(input)
|
|
});
|
|
}
|
|
|
|
export function getCleaningSettlementDetail(session: ApiSession, settlementId: string) {
|
|
return request<CleaningSettlementDetail>(
|
|
session,
|
|
`/cleaning/settlements/${encodeURIComponent(settlementId)}`
|
|
);
|
|
}
|
|
|
|
export function confirmCleaningSettlement(session: ApiSession, settlementId: string, note?: string) {
|
|
return request<CleaningSettlement>(
|
|
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<WechatTransferPreflight>(
|
|
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<CleaningSettlement>(
|
|
session,
|
|
`/cleaning/settlements/${encodeURIComponent(settlementId)}/payout-failure`,
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify(input)
|
|
}
|
|
);
|
|
}
|