/** * 商品分类树数据服务 * 对接化工商品数据分类树API * API文档: category_tree_openapi.json */ import { getApiBase } from '@utils/apiConfig'; // 类型定义 export interface TreeMetric { metric_id: string; metric_name: string; source: 'SMM' | 'Mysteel'; frequency: string; unit: string; description?: string; } export interface TreeNode { name: string; path: string; level: number; children?: TreeNode[]; metrics?: TreeMetric[]; } export interface CategoryTreeResponse { source: 'SMM' | 'Mysteel'; total_metrics: number; tree: TreeNode[]; } export interface ErrorResponse { detail: string; } export interface MetricDataPoint { date: string; value: number | null; } export interface MetricDataResponse { metric_id: string; metric_name: string; source: string; frequency: string; unit: string; data: MetricDataPoint[]; total_count: number; } /** * 获取分类树(支持深度控制) * @param source 数据源类型 ('SMM' | 'Mysteel') * @param maxDepth 返回的最大层级深度(默认1层,推荐懒加载) * @returns 分类树数据 */ export const fetchCategoryTree = async ( source: 'SMM' | 'Mysteel', maxDepth: number = 1 ): Promise => { try { const response = await fetch( `/category-api/api/category-tree?source=${source}&max_depth=${maxDepth}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, } ); if (!response.ok) { const errorData: ErrorResponse = await response.json(); throw new Error(errorData.detail || `HTTP ${response.status}`); } const data: CategoryTreeResponse = await response.json(); return data; } catch (error) { console.error('fetchCategoryTree error:', error); throw error; } }; /** * 获取特定节点及其子树 * @param path 节点完整路径(用 | 分隔) * @param source 数据源类型 ('SMM' | 'Mysteel') * @param maxDepth 返回的子树最大层级深度(默认1层,只返回直接子节点) * @returns 节点数据及其子树 */ export const fetchCategoryNode = async ( path: string, source: 'SMM' | 'Mysteel', maxDepth: number = 1 ): Promise => { try { const encodedPath = encodeURIComponent(path); const response = await fetch( `/category-api/api/category-tree/node?path=${encodedPath}&source=${source}&max_depth=${maxDepth}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, } ); if (!response.ok) { const errorData: ErrorResponse = await response.json(); throw new Error(errorData.detail || `HTTP ${response.status}`); } const data: TreeNode = await response.json(); return data; } catch (error) { console.error('fetchCategoryNode error:', error); throw error; } }; export interface MetricSearchResult { source: string; metric_id: string; metric_name: string; unit: string; frequency: string; category_path: string; description?: string; score?: number; } export interface SearchResponse { total: number; results: MetricSearchResult[]; query: string; } /** * 搜索指标 * @param keywords 搜索关键词(支持空格分隔多个词) * @param source 数据源过滤(可选) * @param frequency 频率过滤(可选) * @param size 返回结果数量(默认100) * @returns 搜索结果 */ export const searchMetrics = async ( keywords: string, source?: 'SMM' | 'Mysteel', frequency?: string, size: number = 100 ): Promise => { try { const params = new URLSearchParams({ keywords, size: size.toString(), }); if (source) params.append('source', source); if (frequency) params.append('frequency', frequency); const response = await fetch(`/category-api/api/search?${params.toString()}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorData: ErrorResponse = await response.json(); throw new Error(errorData.detail || `HTTP ${response.status}`); } const data: SearchResponse = await response.json(); return data; } catch (error) { console.error('searchMetrics error:', error); throw error; } }; /** * 从树中提取所有指标(用于前端搜索) * @param nodes 树节点数组 * @returns 所有指标的扁平化数组 */ export const extractAllMetrics = (nodes: TreeNode[]): TreeMetric[] => { const metrics: TreeMetric[] = []; const traverse = (node: TreeNode) => { if (node.metrics && node.metrics.length > 0) { metrics.push(...node.metrics); } if (node.children && node.children.length > 0) { node.children.forEach(traverse); } }; nodes.forEach(traverse); return metrics; }; /** * 在树中查找节点 * @param nodes 树节点数组 * @param path 节点路径 * @returns 找到的节点或 null */ export const findNodeByPath = (nodes: TreeNode[], path: string): TreeNode | null => { for (const node of nodes) { if (node.path === path) { return node; } if (node.children) { const found = findNodeByPath(node.children, path); if (found) { return found; } } } return null; }; /** * 获取节点的所有父节点路径 * @param path 节点路径(用 | 分隔) * @returns 父节点路径数组 */ export const getParentPaths = (path: string): string[] => { const parts = path.split('|'); const parentPaths: string[] = []; for (let i = 1; i < parts.length; i++) { parentPaths.push(parts.slice(0, i).join('|')); } return parentPaths; }; /** * 获取指标数据详情 * @param metricId 指标ID * @param startDate 开始日期(可选,格式:YYYY-MM-DD) * @param endDate 结束日期(可选,格式:YYYY-MM-DD) * @param limit 返回数据条数(可选,默认100) * @returns 指标数据 */ export const fetchMetricData = async ( metricId: string, startDate?: string, endDate?: string, limit: number = 100 ): Promise => { try { const params = new URLSearchParams({ metric_id: metricId, limit: limit.toString(), }); if (startDate) params.append('start_date', startDate); if (endDate) params.append('end_date', endDate); const response = await fetch(`/category-api/api/metric-data?${params.toString()}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorData: ErrorResponse = await response.json(); throw new Error(errorData.detail || `HTTP ${response.status}`); } const data: MetricDataResponse = await response.json(); return data; } catch (error) { console.error('fetchMetricData error:', error); throw error; } };