/** 绑定门店(与 ThAppLoginOutputDto.locations 一致) */ export interface ThAppBoundLocationDto { id: string; locationCode: string; locationName: string; fullAddress: string; state: boolean; } export interface ThAppLoginInput { tenantId: string; email: string; password: string; uuid?: string; code?: string; } export interface ThAppLoginOutputDto { token: string; refreshToken: string; tenantId: string; tenantName: string; locations: ThAppBoundLocationDto[]; } /** 防止重复提交导致前一个请求被 Abort、后一个已在 Network 成功但 UI 仍报错 */ let loginInFlight: Promise | null = null; function getApiBase(): string { const raw = (import.meta.env.VITE_GLOB_API_URL as string | undefined) ?? 'http://saas-test.3ffoodsafety.com/api/app'; return raw.replace(/^"|"$/g, '').replace(/\/$/, ''); } function normalizeLocation(raw: Record): ThAppBoundLocationDto { return { id: String(raw.id ?? raw.Id ?? ''), locationCode: String(raw.locationCode ?? raw.LocationCode ?? ''), locationName: String(raw.locationName ?? raw.LocationName ?? ''), fullAddress: String(raw.fullAddress ?? raw.FullAddress ?? ''), state: raw.state !== false && raw.State !== false, }; } function normalizeLoginOutput(raw: unknown): ThAppLoginOutputDto { const o = (raw && typeof raw === 'object' ? raw : {}) as Record; const locs = o.locations ?? o.Locations; const arr = Array.isArray(locs) ? locs : []; return { token: String(o.token ?? o.Token ?? ''), refreshToken: String(o.refreshToken ?? o.RefreshToken ?? ''), tenantId: String(o.tenantId ?? o.TenantId ?? ''), tenantName: String(o.tenantName ?? o.TenantName ?? ''), locations: arr.map((x) => normalizeLocation(x as Record), ), }; } function normalizeLocationList(raw: unknown): ThAppBoundLocationDto[] { const arr = Array.isArray(raw) ? raw : []; return arr.map((x) => normalizeLocation(x as Record)); } function loginNetworkError(): Error { return new Error( `登录网络异常:无法连接 ${getApiBase()},请检查网络、后端是否可用,以及是否已配置 CORS 允许本地前端域名`, ); } /** * POST /api/app/th-app-auth/login(匿名) * 与租户下拉一致,走 requestClient 直连 VITE_GLOB_API_URL(不再使用 XHR + dev 代理) */ export async function thAppLogin( input: ThAppLoginInput, ): Promise { if (loginInFlight) { return loginInFlight; } loginInFlight = (async () => { const { requestClient } = await import('#/api/request'); try { const raw = await requestClient.post( 'th-app-auth/login', { tenantId: input.tenantId, email: input.email.trim(), password: input.password, ...(input.uuid ? { uuid: input.uuid } : {}), ...(input.code != null && input.code !== '' ? { code: input.code } : {}), }, { errorMessageMode: 'none', successMessageMode: 'none', headers: { __tenant: input.tenantId, }, }, ); return normalizeLoginOutput(raw); } catch (error) { if (error instanceof Error && error.message.trim()) { throw error; } throw loginNetworkError(); } })().finally(() => { loginInFlight = null; }); return loginInFlight; } /** GET /api/app/th-app-auth/my-locations(Bearer + 租户上下文) */ export async function thAppMyLocations(): Promise { const { requestClient } = await import('#/api/request'); const raw = await requestClient.get('th-app-auth/my-locations', { timeout: 30_000, }); return normalizeLocationList(raw); }