Files
vf_react/src/services/categoryService.ts
2025-11-20 16:59:09 +08:00

283 lines
6.8 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.

/**
* 商品分类树数据服务
* 对接化工商品数据分类树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<CategoryTreeResponse> => {
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<TreeNode> => {
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<SearchResponse> => {
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<MetricDataResponse> => {
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;
}
};