update pay function
This commit is contained in:
@@ -19,12 +19,170 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "搜索",
|
||||||
|
"description": "指标搜索相关接口"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "分类树",
|
"name": "分类树",
|
||||||
"description": "分类树状结构相关接口"
|
"description": "分类树状结构相关接口"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "数据查询",
|
||||||
|
"description": "指标时间序列数据查询接口"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/api/search": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"搜索"
|
||||||
|
],
|
||||||
|
"summary": "搜索化工商品指标",
|
||||||
|
"description": "基于Elasticsearch的多关键词模糊搜索,支持智能分词和相关度排序。\n\n## 功能特点\n- **多关键词搜索**: 支持空格分隔多个关键词,自动AND逻辑组合\n- **模糊匹配**: 自动容错1-2个字符的拼写错误\n- **多字段匹配**: 同时搜索指标名称、分类路径等多个字段\n- **相关度排序**: 自动按匹配度评分排序,最相关的结果排在前面\n- **灵活过滤**: 支持按数据源(SMM/Mysteel)和频率(日/周/月)过滤\n\n## 搜索字段权重\n- 指标名称(metric_name): 权重最高 (3x)\n- 分类层级(category_levels): 权重中等 (2x)\n- 分类路径(category_path): 权重中等 (2x)\n\n## 使用场景\n- 用户输入关键词快速查找指标\n- 自动补全和搜索建议\n- 按类别和数据源筛选指标\n\n## 搜索示例\n- 搜索\"电解液 产量\": 查找包含\"电解液\"和\"产量\"的指标\n- 搜索\"硫酸钴\": 查找所有硫酸钴相关指标\n- 搜索\"焦炭 价格 日\": 查找焦炭日度价格数据\n",
|
||||||
|
"operationId": "searchMetrics",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "keywords",
|
||||||
|
"in": "query",
|
||||||
|
"description": "搜索关键词,支持空格分隔多个词。\n\n示例:\n- \"电解液 产量\" - 查找同时包含这两个词的指标\n- \"硫酸钴\" - 查找硫酸钴相关指标\n- \"焦炭 价格\" - 查找焦炭价格数据\n",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "电解液 产量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source",
|
||||||
|
"in": "query",
|
||||||
|
"description": "数据源过滤(可选)。\n\n- SMM: 上海有色网数据\n- Mysteel: 我的钢铁网数据\n- 不指定: 搜索所有数据源\n",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"SMM",
|
||||||
|
"Mysteel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"example": "SMM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frequency",
|
||||||
|
"in": "query",
|
||||||
|
"description": "数据频率过滤(可选)。\n\n- 日: 日度数据\n- 周: 周度数据\n- 月: 月度数据\n- 不指定: 搜索所有频率\n",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"日",
|
||||||
|
"周",
|
||||||
|
"月"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"example": "日"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "size",
|
||||||
|
"in": "query",
|
||||||
|
"description": "返回结果数量限制",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 1000,
|
||||||
|
"default": 100
|
||||||
|
},
|
||||||
|
"example": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功返回搜索结果",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SearchResponse"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"基础搜索示例": {
|
||||||
|
"value": {
|
||||||
|
"total": 50,
|
||||||
|
"query": "电解液 产量",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"source": "SMM",
|
||||||
|
"metric_id": "12345",
|
||||||
|
"metric_name": "SMM中国电解液月度产量",
|
||||||
|
"unit": "吨",
|
||||||
|
"frequency": "月",
|
||||||
|
"category_path": "新能源|电解液|产量|SMM中国电解液月度产量",
|
||||||
|
"description": "",
|
||||||
|
"score": 15.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "SMM",
|
||||||
|
"metric_id": "12346",
|
||||||
|
"metric_name": "SMM中国电解液周度产量",
|
||||||
|
"unit": "吨",
|
||||||
|
"frequency": "周",
|
||||||
|
"category_path": "新能源|电解液|产量|SMM中国电解液周度产量",
|
||||||
|
"description": "",
|
||||||
|
"score": 14.2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"过滤搜索示例": {
|
||||||
|
"value": {
|
||||||
|
"total": 15,
|
||||||
|
"query": "硫酸钴",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"source": "SMM",
|
||||||
|
"metric_id": "23456",
|
||||||
|
"metric_name": "SMM中国硫酸钴月度产量",
|
||||||
|
"unit": "吨",
|
||||||
|
"frequency": "月",
|
||||||
|
"category_path": "小金属|钴|钴化合物|硫酸钴|产量|SMM中国硫酸钴月度产量",
|
||||||
|
"description": "",
|
||||||
|
"score": 18.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "请求参数错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"detail": "keywords参数不能为空"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务器内部错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"detail": "搜索服务暂时不可用"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/category-tree": {
|
"/api/category-tree": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -239,10 +397,254 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api/metric-data": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"数据查询"
|
||||||
|
],
|
||||||
|
"summary": "获取指标时间序列数据",
|
||||||
|
"description": "根据指标ID查询历史时间序列数据,自动识别数据源(SMM或Mysteel)。\n\n## 功能特点\n- **自动识别数据源**: 无需指定source参数,系统自动查找\n- **灵活的日期范围**: 支持可选的开始/结束日期过滤\n- **数据限制**: 支持limit参数控制返回数据量\n\n## 日期格式支持\n- YYYY-MM-DD (推荐): \"2024-01-01\"\n- YYYYMMDD: \"20240101\"\n- YYYYMMDDHHmmss: \"20240101000000\"(只取日期部分)\n\n## 使用场景\n- 用户点击树节点查看指标数据\n- 图表展示时间序列数据\n- 数据导出和分析\n",
|
||||||
|
"operationId": "getMetricData",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "metric_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "指标唯一ID",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "12345"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "start_date",
|
||||||
|
"in": "query",
|
||||||
|
"description": "开始日期(可选),格式 YYYY-MM-DD 或 YYYYMMDD",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "2024-01-01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "end_date",
|
||||||
|
"in": "query",
|
||||||
|
"description": "结束日期(可选),格式 YYYY-MM-DD 或 YYYYMMDD",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"example": "2024-12-31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description": "返回数据条数限制(1-10000)",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 10000,
|
||||||
|
"default": 100
|
||||||
|
},
|
||||||
|
"example": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功返回指标数据",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MetricDataResponse"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"SMM数据示例": {
|
||||||
|
"value": {
|
||||||
|
"metric_id": "12345",
|
||||||
|
"metric_name": "SMM中国硫酸钴月度产量",
|
||||||
|
"source": "SMM",
|
||||||
|
"frequency": "月",
|
||||||
|
"unit": "吨",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"date": "2024-12-01",
|
||||||
|
"value": 12500.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-11-01",
|
||||||
|
"value": 12300.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-10-01",
|
||||||
|
"value": 12100.8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_count": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Mysteel数据示例": {
|
||||||
|
"value": {
|
||||||
|
"metric_id": "A0101010",
|
||||||
|
"metric_name": "唐山焦炭价格",
|
||||||
|
"source": "MYSTEEL",
|
||||||
|
"frequency": "日",
|
||||||
|
"unit": "元/吨",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"date": "2024-12-20",
|
||||||
|
"value": 2350.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2024-12-19",
|
||||||
|
"value": 2340.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_count": 365
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "未找到指定指标",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"detail": "未找到指标: metric_id=99999"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "请求参数错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"detail": "limit参数必须在1-10000之间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "服务器内部错误",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorResponse"
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"detail": "查询数据失败: [具体错误信息]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"SearchResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "搜索结果响应对象",
|
||||||
|
"required": [
|
||||||
|
"total",
|
||||||
|
"query",
|
||||||
|
"results"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"total": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "搜索结果总数",
|
||||||
|
"example": 50
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "查询关键词",
|
||||||
|
"example": "电解液 产量"
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "指标列表(按相关度评分降序)",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/MetricInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MetricInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "指标信息对象",
|
||||||
|
"required": [
|
||||||
|
"source",
|
||||||
|
"metric_id",
|
||||||
|
"metric_name",
|
||||||
|
"unit",
|
||||||
|
"frequency",
|
||||||
|
"category_path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"source": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据源",
|
||||||
|
"enum": [
|
||||||
|
"SMM",
|
||||||
|
"Mysteel"
|
||||||
|
],
|
||||||
|
"example": "SMM"
|
||||||
|
},
|
||||||
|
"metric_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "指标唯一ID",
|
||||||
|
"example": "12345"
|
||||||
|
},
|
||||||
|
"metric_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "指标名称",
|
||||||
|
"example": "SMM中国硫酸钴月度产量"
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据单位",
|
||||||
|
"example": "吨"
|
||||||
|
},
|
||||||
|
"frequency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据频率",
|
||||||
|
"enum": [
|
||||||
|
"日",
|
||||||
|
"周",
|
||||||
|
"月"
|
||||||
|
],
|
||||||
|
"example": "月"
|
||||||
|
},
|
||||||
|
"category_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "完整分类路径(用|分隔)",
|
||||||
|
"example": "小金属|钴|钴化合物|硫酸钴|产量|SMM中国硫酸钴月度产量"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "指标描述备注",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"score": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "搜索相关度评分(仅搜索结果返回)",
|
||||||
|
"nullable": true,
|
||||||
|
"example": 15.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"CategoryTreeResponse": {
|
"CategoryTreeResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "分类树响应对象",
|
"description": "分类树响应对象",
|
||||||
@@ -374,6 +776,88 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MetricDataResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "指标数据查询响应对象",
|
||||||
|
"required": [
|
||||||
|
"metric_id",
|
||||||
|
"metric_name",
|
||||||
|
"source",
|
||||||
|
"frequency",
|
||||||
|
"unit",
|
||||||
|
"data",
|
||||||
|
"total_count"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"metric_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "指标唯一ID",
|
||||||
|
"example": "12345"
|
||||||
|
},
|
||||||
|
"metric_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "指标名称",
|
||||||
|
"example": "SMM中国硫酸钴月度产量"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据源",
|
||||||
|
"enum": [
|
||||||
|
"SMM",
|
||||||
|
"MYSTEEL"
|
||||||
|
],
|
||||||
|
"example": "SMM"
|
||||||
|
},
|
||||||
|
"frequency": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据频率",
|
||||||
|
"enum": [
|
||||||
|
"日",
|
||||||
|
"周",
|
||||||
|
"月"
|
||||||
|
],
|
||||||
|
"example": "月"
|
||||||
|
},
|
||||||
|
"unit": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "数据单位",
|
||||||
|
"example": "吨"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "时间序列数据点列表(按日期倒序)",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/DataPoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "符合条件的数据总条数",
|
||||||
|
"example": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DataPoint": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "单个数据点",
|
||||||
|
"required": [
|
||||||
|
"date",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "日期,格式 YYYY-MM-DD",
|
||||||
|
"example": "2024-01-01"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "数值(可能为null)",
|
||||||
|
"nullable": true,
|
||||||
|
"example": 1234.56
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ErrorResponse": {
|
"ErrorResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "错误响应对象",
|
"description": "错误响应对象",
|
||||||
|
|||||||
@@ -118,34 +118,59 @@ export const fetchCategoryNode = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 query 搜索关键词
|
* @param keywords 搜索关键词(支持空格分隔多个词)
|
||||||
* @param source 数据源类型 ('SMM' | 'Mysteel')
|
* @param source 数据源过滤(可选)
|
||||||
* @returns 匹配的指标列表
|
* @param frequency 频率过滤(可选)
|
||||||
|
* @param size 返回结果数量(默认100)
|
||||||
|
* @returns 搜索结果
|
||||||
*/
|
*/
|
||||||
export const searchMetrics = async (
|
export const searchMetrics = async (
|
||||||
query: string,
|
keywords: string,
|
||||||
source: 'SMM' | 'Mysteel'
|
source?: 'SMM' | 'Mysteel',
|
||||||
): Promise<TreeMetric[]> => {
|
frequency?: string,
|
||||||
|
size: number = 100
|
||||||
|
): Promise<SearchResponse> => {
|
||||||
try {
|
try {
|
||||||
// 注意:这个接口可能需要后端额外实现
|
const params = new URLSearchParams({
|
||||||
// 如果后端没有提供搜索接口,可以在前端基于完整树进行过滤
|
keywords,
|
||||||
const response = await fetch(
|
size: size.toString(),
|
||||||
`/category-api/api/metrics/search?query=${encodeURIComponent(query)}&source=${source}`,
|
});
|
||||||
{
|
|
||||||
|
if (source) params.append('source', source);
|
||||||
|
if (frequency) params.append('frequency', frequency);
|
||||||
|
|
||||||
|
const response = await fetch(`/category-api/api/search?${params.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP ${response.status}`);
|
const errorData: ErrorResponse = await response.json();
|
||||||
|
throw new Error(errorData.detail || `HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: TreeMetric[] = await response.json();
|
const data: SearchResponse = await response.json();
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('searchMetrics error:', error);
|
console.error('searchMetrics error:', error);
|
||||||
|
|||||||
@@ -34,7 +34,16 @@ import {
|
|||||||
FaEye,
|
FaEye,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { fetchCategoryTree, fetchCategoryNode, TreeNode, TreeMetric, CategoryTreeResponse } from '@services/categoryService';
|
import {
|
||||||
|
fetchCategoryTree,
|
||||||
|
fetchCategoryNode,
|
||||||
|
searchMetrics,
|
||||||
|
TreeNode,
|
||||||
|
TreeMetric,
|
||||||
|
CategoryTreeResponse,
|
||||||
|
MetricSearchResult,
|
||||||
|
SearchResponse
|
||||||
|
} from '@services/categoryService';
|
||||||
import MetricDataModal from './MetricDataModal';
|
import MetricDataModal from './MetricDataModal';
|
||||||
|
|
||||||
// 黑金主题配色
|
// 黑金主题配色
|
||||||
@@ -253,6 +262,8 @@ const DataBrowser: React.FC = () => {
|
|||||||
const [treeData, setTreeData] = useState<CategoryTreeResponse | null>(null);
|
const [treeData, setTreeData] = useState<CategoryTreeResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [searchResults, setSearchResults] = useState<SearchResponse | null>(null);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
const [currentNode, setCurrentNode] = useState<TreeNode | null>(null);
|
const [currentNode, setCurrentNode] = useState<TreeNode | null>(null);
|
||||||
const [breadcrumbs, setBreadcrumbs] = useState<string[]>([]);
|
const [breadcrumbs, setBreadcrumbs] = useState<string[]>([]);
|
||||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
||||||
@@ -275,6 +286,7 @@ const DataBrowser: React.FC = () => {
|
|||||||
setCurrentNode(null);
|
setCurrentNode(null);
|
||||||
setBreadcrumbs([]);
|
setBreadcrumbs([]);
|
||||||
setExpandedNodes(new Set());
|
setExpandedNodes(new Set());
|
||||||
|
setSearchResults(null); // 清空搜索结果
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: '加载失败',
|
title: '加载失败',
|
||||||
@@ -288,6 +300,43 @@ const DataBrowser: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 执行搜索
|
||||||
|
const handleSearch = async () => {
|
||||||
|
if (!searchQuery.trim()) {
|
||||||
|
setSearchResults(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearching(true);
|
||||||
|
try {
|
||||||
|
const results = await searchMetrics(searchQuery, selectedSource, undefined, 100);
|
||||||
|
setSearchResults(results);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: '搜索失败',
|
||||||
|
description: '无法搜索指标数据',
|
||||||
|
status: 'error',
|
||||||
|
duration: 3000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setSearching(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当搜索关键词变化时,自动搜索
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (searchQuery.trim()) {
|
||||||
|
handleSearch();
|
||||||
|
} else {
|
||||||
|
setSearchResults(null);
|
||||||
|
}
|
||||||
|
}, 500); // 防抖 500ms
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchQuery, selectedSource]);
|
||||||
|
|
||||||
// 切换节点展开状态(懒加载子节点)
|
// 切换节点展开状态(懒加载子节点)
|
||||||
const toggleNodeExpand = async (node: TreeNode) => {
|
const toggleNodeExpand = async (node: TreeNode) => {
|
||||||
const isCurrentlyExpanded = expandedNodes.has(node.path);
|
const isCurrentlyExpanded = expandedNodes.has(node.path);
|
||||||
@@ -401,28 +450,12 @@ const DataBrowser: React.FC = () => {
|
|||||||
onOpen();
|
onOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 过滤树节点(根据搜索关键词)
|
// 显示的树节点(搜索时不显示树)
|
||||||
const filteredTree = useMemo(() => {
|
const displayTree = useMemo(() => {
|
||||||
if (!treeData || !searchQuery) return treeData?.tree || [];
|
if (searchQuery.trim()) {
|
||||||
|
return []; // 搜索时不显示树
|
||||||
const filterNodes = (nodes: TreeNode[]): TreeNode[] => {
|
|
||||||
return nodes
|
|
||||||
.map((node) => {
|
|
||||||
const matchesName = node.name.toLowerCase().includes(searchQuery.toLowerCase());
|
|
||||||
const filteredChildren = node.children ? filterNodes(node.children) : [];
|
|
||||||
|
|
||||||
if (matchesName || filteredChildren.length > 0) {
|
|
||||||
return {
|
|
||||||
...node,
|
|
||||||
children: filteredChildren,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return null;
|
return treeData?.tree || [];
|
||||||
})
|
|
||||||
.filter(Boolean) as TreeNode[];
|
|
||||||
};
|
|
||||||
|
|
||||||
return filterNodes(treeData.tree);
|
|
||||||
}, [treeData, searchQuery]);
|
}, [treeData, searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -518,6 +551,7 @@ const DataBrowser: React.FC = () => {
|
|||||||
mb={6}
|
mb={6}
|
||||||
>
|
>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
<VStack spacing={3} align="stretch">
|
||||||
<HStack spacing={4}>
|
<HStack spacing={4}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索分类或指标名称..."
|
placeholder="搜索分类或指标名称..."
|
||||||
@@ -537,6 +571,8 @@ const DataBrowser: React.FC = () => {
|
|||||||
bg={themeColors.primary.gold}
|
bg={themeColors.primary.gold}
|
||||||
color={themeColors.bg.primary}
|
color={themeColors.bg.primary}
|
||||||
_hover={{ bg: themeColors.primary.goldLight }}
|
_hover={{ bg: themeColors.primary.goldLight }}
|
||||||
|
onClick={handleSearch}
|
||||||
|
isLoading={searching}
|
||||||
>
|
>
|
||||||
搜索
|
搜索
|
||||||
</Button>
|
</Button>
|
||||||
@@ -552,6 +588,27 @@ const DataBrowser: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
|
{/* 搜索结果提示 */}
|
||||||
|
{searchResults && (
|
||||||
|
<Flex align="center" justify="space-between" py={2}>
|
||||||
|
<Text color={themeColors.text.secondary} fontSize="sm">
|
||||||
|
找到 <Text as="span" color={themeColors.primary.gold} fontWeight="bold">{searchResults.total}</Text> 个相关指标
|
||||||
|
</Text>
|
||||||
|
<Text color={themeColors.text.muted} fontSize="xs">
|
||||||
|
关键词: "{searchResults.query}"
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{searching && (
|
||||||
|
<Flex align="center" justify="center" py={2}>
|
||||||
|
<Spinner size="sm" color={themeColors.primary.gold} mr={2} />
|
||||||
|
<Text color={themeColors.text.secondary} fontSize="sm">
|
||||||
|
搜索中...
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</MotionBox>
|
</MotionBox>
|
||||||
@@ -628,9 +685,77 @@ const DataBrowser: React.FC = () => {
|
|||||||
<Flex justify="center" align="center" py={10}>
|
<Flex justify="center" align="center" py={10}>
|
||||||
<Spinner color={themeColors.primary.gold} size="xl" />
|
<Spinner color={themeColors.primary.gold} size="xl" />
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
) : searchQuery.trim() ? (
|
||||||
|
// 搜索模式:显示搜索结果列表
|
||||||
<VStack align="stretch" spacing={1}>
|
<VStack align="stretch" spacing={1}>
|
||||||
{filteredTree.map((node) => (
|
{searchResults && searchResults.results.length > 0 ? (
|
||||||
|
searchResults.results.map((result) => (
|
||||||
|
<Box
|
||||||
|
key={result.metric_id}
|
||||||
|
p={3}
|
||||||
|
cursor="pointer"
|
||||||
|
bg="transparent"
|
||||||
|
_hover={{ bg: themeColors.bg.cardHover }}
|
||||||
|
borderRadius="md"
|
||||||
|
borderLeftWidth="3px"
|
||||||
|
borderLeftColor="transparent"
|
||||||
|
_hover={{ borderLeftColor: themeColors.primary.gold }}
|
||||||
|
transition="all 0.2s"
|
||||||
|
onClick={() => {
|
||||||
|
// 转换搜索结果为 TreeMetric 格式
|
||||||
|
const metric: TreeMetric = {
|
||||||
|
metric_id: result.metric_id,
|
||||||
|
metric_name: result.metric_name,
|
||||||
|
source: result.source,
|
||||||
|
frequency: result.frequency,
|
||||||
|
unit: result.unit,
|
||||||
|
description: result.description,
|
||||||
|
};
|
||||||
|
handleMetricClick(metric);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text color={themeColors.text.primary} fontSize="sm" fontWeight="bold" flex="1">
|
||||||
|
{result.metric_name}
|
||||||
|
</Text>
|
||||||
|
<Badge
|
||||||
|
bg={result.source === 'SMM' ? 'blue.500' : 'green.500'}
|
||||||
|
color="white"
|
||||||
|
fontSize="xs"
|
||||||
|
>
|
||||||
|
{result.source}
|
||||||
|
</Badge>
|
||||||
|
</HStack>
|
||||||
|
<HStack spacing={4} fontSize="xs" color={themeColors.text.muted}>
|
||||||
|
<Text>路径: {result.category_path}</Text>
|
||||||
|
<Text>频率: {result.frequency}</Text>
|
||||||
|
<Text>单位: {result.unit || '-'}</Text>
|
||||||
|
</HStack>
|
||||||
|
{result.score && (
|
||||||
|
<Text fontSize="xs" color={themeColors.text.muted}>
|
||||||
|
相关度: {(result.score * 100).toFixed(0)}%
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : searchResults ? (
|
||||||
|
<Flex justify="center" align="center" py={10}>
|
||||||
|
<VStack spacing={3}>
|
||||||
|
<Icon as={FaSearch} color={themeColors.text.muted} boxSize={12} />
|
||||||
|
<Text color={themeColors.text.muted}>未找到匹配的指标</Text>
|
||||||
|
<Text color={themeColors.text.muted} fontSize="sm">
|
||||||
|
尝试使用不同的关键词
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Flex>
|
||||||
|
) : null}
|
||||||
|
</VStack>
|
||||||
|
) : (
|
||||||
|
// 正常模式:显示分类树
|
||||||
|
<VStack align="stretch" spacing={1}>
|
||||||
|
{displayTree.map((node) => (
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
key={node.path}
|
key={node.path}
|
||||||
node={node}
|
node={node}
|
||||||
@@ -638,7 +763,7 @@ const DataBrowser: React.FC = () => {
|
|||||||
onNodeClick={handleNodeClick}
|
onNodeClick={handleNodeClick}
|
||||||
expandedNodes={expandedNodes}
|
expandedNodes={expandedNodes}
|
||||||
onToggleExpand={toggleNodeExpand}
|
onToggleExpand={toggleNodeExpand}
|
||||||
searchQuery={searchQuery}
|
searchQuery=""
|
||||||
loadingNodes={loadingNodes}
|
loadingNodes={loadingNodes}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user