Files
worldquant-alpha-system/frontend/src/api.ts
T

125 lines
3.5 KiB
TypeScript
Raw Normal View History

export class ApiError extends Error {
constructor(
message: string,
public status: number,
) {
super(message);
}
}
export async function api<T>(
path: string,
options: RequestInit = {},
): Promise<T> {
const response = await fetch(`/api/v1${path}`, {
...options,
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-WQ-Request": "1",
...options.headers,
},
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
if (response.status === 401 && path !== "/auth/login")
window.dispatchEvent(new Event("session-expired"));
throw new ApiError(
typeof body.detail === "string"
? body.detail
: `请求失败(${response.status})`,
response.status,
);
}
return response.json() as Promise<T>;
}
export const post = <T>(path: string, body?: unknown) =>
api<T>(path, {
method: "POST",
body: body === undefined ? undefined : JSON.stringify(body),
});
export const patch = <T>(path: string, body: unknown) =>
api<T>(path, { method: "PATCH", body: JSON.stringify(body) });
export function formatNumber(value: unknown, digits = 2): string {
return typeof value === "number" && Number.isFinite(value)
? value.toFixed(digits)
: "—";
}
export function displayValue(value: unknown): string {
if (value === null || value === undefined || value === "") return "未提供";
if (typeof value === "object") return JSON.stringify(value);
return String(value);
}
export function formatTime(
value: string | null | undefined,
timezone = "Asia/Shanghai",
): string {
if (!value) return "尚未同步";
const date = new Date(value);
if (!Number.isFinite(date.valueOf())) return "时间未知";
return new Intl.DateTimeFormat("zh-CN", {
timeZone: timezone,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(date);
}
export const stateLabels = {
inbox: "待研究",
candidate: "候选",
optimizing: "优化中",
archived: "归档",
};
export const stateOptions = Object.entries(stateLabels).map(
([value, label]) => ({ value, label }),
);
export const jobLabels: Record<string, string> = {
catalog_full_sync: "全量同步数据目录与字段",
catalog_sync: "同步数据集目录",
field_sync: "同步数据字段",
full_sync: "全量同步 Alpha",
daily_sync: "按天同步 Alpha",
self_correlation: "本地自相关检测",
submission_check: "写回 Description 并检查提交",
alpha_refresh: "导入 / 刷新 Alpha",
pnl_refresh: "获取 PnL",
pnl_backfill: "检查已提交 Alpha 的 PnL",
connect: "连接 WorldQuant",
verify: "继续人工验证",
profile: "刷新个人资料",
};
export const correlationLabels = {
high: "相关性偏高",
low: "低于阈值",
partial: "样本不完整",
insufficient_data: "数据不足",
};
export const jobStateLabels: Record<string, string> = {
queued: "排队中",
running: "执行中",
waiting_auth: "等待人工验证",
waiting_connection: "等待连接",
completed: "已完成",
completed_with_errors: "部分失败",
failed: "失败",
cancelled: "已取消",
};
export function queryString(
values: Record<string, string | number | boolean | undefined>,
): string {
return new URLSearchParams(
Object.entries(values)
.filter(([, v]) => v !== undefined && v !== "")
.map(([k, v]) => [k, String(v)]),
).toString();
}