看股功能更新

This commit is contained in:
2026-08-16 00:05:26 +08:00
parent 9cce670b74
commit fc86fe0674
28 changed files with 3823 additions and 96 deletions

View File

@@ -18,6 +18,9 @@ import type {
SyncRequest,
SyncResponse,
Timeframe,
TradesClearResponse,
TradesImportResponse,
UserTrade,
} from './types';
// dev 用 Vite 代理(/api -> :8000生产构建设 VITE_API_BASE 指向后端地址。
@@ -37,8 +40,11 @@ async function readError(res: Response, fallback: string): Promise<string> {
const body = await res.text();
if (!body) return fallback;
try {
const data = JSON.parse(body) as { detail?: string };
return data.detail || fallback;
const data = JSON.parse(body) as { detail?: unknown };
// FastAPI 校验类 422 的 detail 是对象数组,直接当字符串用会显示成 [object Object]
if (typeof data.detail === 'string') return data.detail;
if (data.detail != null) return JSON.stringify(data.detail);
return fallback;
} catch {
return body;
}
@@ -49,7 +55,9 @@ async function apiFetch(path: string, init: RequestInit = {}): Promise<Response>
...init,
credentials: 'include',
headers: {
...(init.body ? { 'Content-Type': 'application/json' } : {}),
// 仅 JSON字符串 body手工设 Content-TypeFormData 必须留给浏览器生成
// multipart 边界,手工设置会导致后端解析失败 422
...(typeof init.body === 'string' ? { 'Content-Type': 'application/json' } : {}),
...init.headers,
},
});
@@ -158,6 +166,8 @@ export async function getStocks(params: {
industry?: string;
area?: string;
watched_only?: boolean;
sort?: string;
order?: 'asc' | 'desc';
limit?: number;
offset?: number;
}): Promise<StockListResponse> {
@@ -167,6 +177,8 @@ export async function getStocks(params: {
if (params.industry) q.set('industry', params.industry);
if (params.area) q.set('area', params.area);
if (params.watched_only) q.set('watched_only', 'true');
if (params.sort) q.set('sort', params.sort);
if (params.order) q.set('order', params.order);
q.set('limit', String(params.limit ?? 100));
q.set('offset', String(params.offset ?? 0));
const res = await apiFetch(`/api/stocks?${q.toString()}`);
@@ -224,3 +236,27 @@ export async function deleteScreenerQuery(id: number): Promise<void> {
const res = await apiFetch(`/api/screener/queries/${id}`, { method: 'DELETE' });
if (!res.ok && res.status !== 401) throw new ApiError(`删除失败 (HTTP ${res.status})`, res.status);
}
// ---------- 交割单(个人实盘买卖点) ----------
export async function getTrades(tsCode?: string): Promise<UserTrade[]> {
const q = tsCode ? `?ts_code=${encodeURIComponent(tsCode)}` : '';
const res = await apiFetch(`/api/trades${q}`);
if (!res.ok) throw new ApiError(await readError(res, `获取成交记录失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as UserTrade[];
}
/** 上传交割单文件CSV/Excel/HTML 均可,后端自动识别列名与编码)。 */
export async function importTrades(file: File): Promise<TradesImportResponse> {
const form = new FormData();
form.append('file', file);
// 注意:不能手工设 Content-TypeFormData 需自带 multipart 边界
const res = await apiFetch('/api/trades/import', { method: 'POST', body: form });
if (!res.ok) throw new ApiError(await readError(res, `导入失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as TradesImportResponse;
}
export async function clearTrades(): Promise<TradesClearResponse> {
const res = await apiFetch('/api/trades', { method: 'DELETE' });
if (!res.ok) throw new ApiError(await readError(res, `清空成交失败 (HTTP ${res.status})`), res.status);
return (await res.json()) as TradesClearResponse;
}