283 lines
6.8 KiB
TypeScript
283 lines
6.8 KiB
TypeScript
/**
|
||
* 商品分类树数据服务
|
||
* 对接化工商品数据分类树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;
|
||
}
|
||
};
|