You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
9.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import type {
AddressInput,
AlertRecord,
ApiResponse,
CreateQuoteResult,
LoginResponse,
MarkupConfig,
MetricsDashboard,
MothershipCandidatesResult,
PaginatedResult,
QueueStats,
QuoteDetail,
QuoteRequestBody,
RpaStatus,
} from "@/lib/frontend/types";
import { CANDIDATES_FETCH_TIMEOUT_MS } from "@/lib/frontend/constants";
async function parseJson<T>(response: Response): Promise<ApiResponse<T>> {
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
return {
code: "INTERNAL_ERROR",
message: response.ok
? "响应解析失败"
: `服务异常 (HTTP ${response.status})`,
data: null,
};
}
try {
return (await response.json()) as ApiResponse<T>;
} catch {
return {
code: "INTERNAL_ERROR",
message: "响应解析失败",
data: null,
};
}
}
function hostHeaders(
serviceToken: string,
customerId: string,
): Record<string, string> {
return {
Authorization: `Bearer ${serviceToken}`,
"x-customer-id": customerId,
"Content-Type": "application/json",
"Cache-Control": "no-store",
};
}
function adminHeaders(token: string): Record<string, string> {
return {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"Cache-Control": "no-store",
};
}
export async function hostCreateQuote(
apiBaseUrl: string,
serviceToken: string,
body: QuoteRequestBody,
): Promise<ApiResponse<CreateQuoteResult>> {
const response = await fetch(`${apiBaseUrl}/api/quotes`, {
method: "POST",
headers: hostHeaders(serviceToken, body.customer_id),
body: JSON.stringify(body),
cache: "no-store",
});
return parseJson(response);
}
export async function hostFetchMothershipCandidates(
apiBaseUrl: string,
serviceToken: string,
customerId: string,
pickup: Pick<AddressInput, "street" | "city" | "state" | "zip">,
delivery: Pick<AddressInput, "street" | "city" | "state" | "zip">,
): Promise<ApiResponse<MothershipCandidatesResult>> {
const controller = new AbortController();
const timeout = setTimeout(
() => controller.abort(),
CANDIDATES_FETCH_TIMEOUT_MS,
);
try {
const response = await fetch(
`${apiBaseUrl}/api/addresses/mothership-candidates`,
{
method: "POST",
headers: hostHeaders(serviceToken, customerId),
body: JSON.stringify({
customer_id: customerId,
pickup_address: pickup,
delivery_address: delivery,
}),
cache: "no-store",
signal: controller.signal,
},
);
return parseJson(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
return {
code: "QUOTE_TIMEOUT",
message: "地址联想超时MotherShip 响应较慢,请稍后重试",
data: null,
};
}
throw error;
} finally {
clearTimeout(timeout);
}
}
export async function hostPreheatQuoteSession(
apiBaseUrl: string,
serviceToken: string,
customerId: string,
quoteSessionId: string,
): Promise<void> {
try {
await fetch(
`${apiBaseUrl}/api/addresses/mothership-candidates/preheat`,
{
method: "POST",
headers: hostHeaders(serviceToken, customerId),
body: JSON.stringify({
customer_id: customerId,
quote_session_id: quoteSessionId,
}),
cache: "no-store",
},
);
} catch {
/* 预热失败不阻断主流程 */
}
}
export async function hostConfirmMothershipAddresses(
apiBaseUrl: string,
serviceToken: string,
customerId: string,
quoteSessionId: string,
pickup: MothershipCandidatesResult["pickup_candidates"][number],
delivery: MothershipCandidatesResult["delivery_candidates"][number],
): Promise<ApiResponse<{ confirmed: boolean }>> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 300_000);
try {
const response = await fetch(
`${apiBaseUrl}/api/addresses/mothership-candidates/confirm`,
{
method: "POST",
headers: hostHeaders(serviceToken, customerId),
body: JSON.stringify({
customer_id: customerId,
quote_session_id: quoteSessionId,
pickup,
delivery,
}),
cache: "no-store",
signal: controller.signal,
},
);
return parseJson(response);
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
return {
code: "QUOTE_TIMEOUT",
message: "地址确认超时,请重试",
data: null,
};
}
throw error;
} finally {
clearTimeout(timeout);
}
}
export async function hostReleaseQuoteSession(
apiBaseUrl: string,
serviceToken: string,
customerId: string,
quoteSessionId: string,
): Promise<void> {
try {
await fetch(
`${apiBaseUrl}/api/addresses/mothership-candidates/release`,
{
method: "POST",
headers: hostHeaders(serviceToken, customerId),
body: JSON.stringify({
customer_id: customerId,
quote_session_id: quoteSessionId,
}),
cache: "no-store",
},
);
} catch {
/* 释放失败不阻断 UI */
}
}
export async function hostGetQuote(
apiBaseUrl: string,
serviceToken: string,
customerId: string,
quoteId: string,
): Promise<ApiResponse<QuoteDetail>> {
const response = await fetch(`${apiBaseUrl}/api/quotes/${quoteId}`, {
method: "GET",
headers: hostHeaders(serviceToken, customerId),
cache: "no-store",
});
return parseJson(response);
}
export async function adminLogin(
apiBaseUrl: string,
username: string,
password: string,
): Promise<ApiResponse<LoginResponse>> {
const response = await fetch(`${apiBaseUrl}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
cache: "no-store",
});
return parseJson(response);
}
export async function adminGetAlerts(
apiBaseUrl: string,
token: string,
page: number,
size: number,
type?: string,
status?: string,
): Promise<ApiResponse<PaginatedResult<AlertRecord>>> {
const params = new URLSearchParams({
page: String(page),
size: String(size),
});
if (type) params.set("type", type);
if (status) params.set("status", status);
const response = await fetch(`${apiBaseUrl}/api/alerts?${params}`, {
headers: adminHeaders(token),
cache: "no-store",
});
return parseJson(response);
}
export async function adminResolveAlert(
apiBaseUrl: string,
token: string,
alertId: string,
resolverId: string,
): Promise<ApiResponse<AlertRecord>> {
const response = await fetch(`${apiBaseUrl}/api/alerts/${alertId}/resolve`, {
method: "POST",
headers: adminHeaders(token),
body: JSON.stringify({ resolver_id: resolverId }),
cache: "no-store",
});
return parseJson(response);
}
export async function adminGetMarkupConfigs(
apiBaseUrl: string,
token: string,
page: number,
size: number,
keyword?: string,
): Promise<ApiResponse<PaginatedResult<MarkupConfig>>> {
const params = new URLSearchParams({
page: String(page),
size: String(size),
});
if (keyword) params.set("keyword", keyword);
const response = await fetch(
`${apiBaseUrl}/api/admin/markup-configs?${params}`,
{
headers: adminHeaders(token),
cache: "no-store",
},
);
return parseJson(response);
}
export async function adminUpdateMarkupConfig(
apiBaseUrl: string,
token: string,
customerId: string,
body: {
markup_type: "percent" | "fixed";
markup_percent?: number;
markup_fixed_amount?: number;
remark?: string;
},
): Promise<ApiResponse<MarkupConfig>> {
const response = await fetch(
`${apiBaseUrl}/api/admin/markup-configs/${customerId}`,
{
method: "PUT",
headers: adminHeaders(token),
body: JSON.stringify(body),
cache: "no-store",
},
);
return parseJson(response);
}
export async function adminGetMetrics(
apiBaseUrl: string,
token: string,
): Promise<ApiResponse<MetricsDashboard>> {
const response = await fetch(`${apiBaseUrl}/api/metrics/dashboard`, {
headers: adminHeaders(token),
cache: "no-store",
});
return parseJson(response);
}
export async function adminGetRpaStatus(
apiBaseUrl: string,
token: string,
): Promise<ApiResponse<RpaStatus>> {
const response = await fetch(`${apiBaseUrl}/api/rpa/status`, {
headers: adminHeaders(token),
cache: "no-store",
});
return parseJson(response);
}
export async function adminGetQueueStats(
apiBaseUrl: string,
token: string,
): Promise<ApiResponse<QueueStats>> {
const response = await fetch(`${apiBaseUrl}/api/admin/queues/stats`, {
headers: adminHeaders(token),
cache: "no-store",
});
return parseJson(response);
}
export async function adminPauseWorker(
apiBaseUrl: string,
token: string,
workerId: string,
durationMinutes: number,
): Promise<ApiResponse<{ worker_id: string; message: string }>> {
const response = await fetch(`${apiBaseUrl}/api/rpa/worker/pause`, {
method: "POST",
headers: adminHeaders(token),
body: JSON.stringify({
worker_id: workerId,
duration_minutes: durationMinutes,
}),
cache: "no-store",
});
return parseJson(response);
}