Fundamental_Analysis/frontend/src/hooks/useApi.ts
xucheng b5a4d2212c feat: 实现动态分析配置并优化前端UI
本次提交引入了一系列重要功能,核心是实现了财务分析模块的动态配置,并对配置和报告页面的用户界面进行了改进。

主要变更:

- **动态配置:**
  - 后端实现了 `ConfigManager` 服务,用于动态管理 `analysis-config.json` 和 `config.json`。
  - 添加了用于读取和更新配置的 API 端点。
  - 开发了前端 `/config` 页面,允许用户实时查看和修改分析配置。

- **后端增强:**
  - 更新了 `AnalysisClient` 和 `CompanyProfileClient` 以使用新的配置系统。
  - 重构了财务数据相关的路由。

- **前端改进:**
  - 新增了可复用的 `Checkbox` UI 组件。
  - 使用更直观和用户友好的界面重新设计了配置页面。
  - 改进了财务报告页面的布局和数据展示。

- **文档与杂务:**
  - 更新了设计和需求文档以反映新功能。
  - 更新了前后端依赖。
  - 修改了开发脚本 `dev.sh`。
2025-10-30 14:50:36 +08:00

114 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import useSWR from 'swr';
import { useConfigStore } from '@/stores/useConfigStore';
import { BatchFinancialDataResponse, FinancialConfigResponse, AnalysisConfigResponse } from '@/types';
const fetcher = async (url: string) => {
const res = await fetch(url);
const contentType = res.headers.get('Content-Type') || '';
const text = await res.text();
// 尝试解析JSON
const tryParseJson = () => {
try { return JSON.parse(text); } catch { return null; }
};
const data = contentType.includes('application/json') ? tryParseJson() : tryParseJson();
if (!res.ok) {
// 后端可能返回纯文本错误,统一抛出可读错误
const message = data && data.detail ? data.detail : (text || `Request failed: ${res.status}`);
throw new Error(message);
}
if (data === null) {
throw new Error('无效的服务器响应非JSON');
}
return data;
};
export function useConfig() {
const { setConfig, setError } = useConfigStore();
const { data, error, isLoading } = useSWR('/api/config', fetcher, {
onSuccess: (data) => setConfig(data),
onError: (err) => setError(err.message),
});
return { data, error, isLoading };
}
export async function updateConfig(newConfig: any) {
const res = await fetch('/api/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newConfig),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
export async function testConfig(type: string, data: any) {
const res = await fetch('/api/config/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config_type: type, config_data: data }),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
export function useFinancialConfig() {
return useSWR<FinancialConfigResponse>('/api/financials/config', fetcher);
}
export function useChinaFinancials(ts_code?: string, years: number = 10) {
return useSWR<BatchFinancialDataResponse>(
ts_code ? `/api/financials/china/${encodeURIComponent(ts_code)}?years=${encodeURIComponent(String(years))}` : null,
fetcher,
{
revalidateOnFocus: false, // 不在窗口聚焦时重新验证
revalidateOnReconnect: false, // 不在网络重连时重新验证
dedupingInterval: 300000, // 5分钟内去重
errorRetryCount: 1, // 错误重试1次
}
);
}
export function useAnalysisConfig() {
return useSWR<AnalysisConfigResponse>('/api/financials/analysis-config', fetcher);
}
export async function updateAnalysisConfig(config: AnalysisConfigResponse) {
const res = await fetch('/api/financials/analysis-config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
export async function generateFullAnalysis(tsCode: string, companyName: string) {
const url = `/api/financials/china/${encodeURIComponent(tsCode)}/analysis?company_name=${encodeURIComponent(companyName)}`;
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const text = await res.text();
if (!res.ok) {
try {
const errorJson = JSON.parse(text);
throw new Error(errorJson.detail || text);
} catch {
throw new Error(text || `Request failed: ${res.status}`);
}
}
try {
return JSON.parse(text);
} catch {
throw new Error('Invalid JSON response from server.');
}
}