update pay function
This commit is contained in:
@@ -34,7 +34,16 @@ import {
|
||||
FaEye,
|
||||
} from 'react-icons/fa';
|
||||
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';
|
||||
|
||||
// 黑金主题配色
|
||||
@@ -253,6 +262,8 @@ const DataBrowser: React.FC = () => {
|
||||
const [treeData, setTreeData] = useState<CategoryTreeResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<SearchResponse | null>(null);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [currentNode, setCurrentNode] = useState<TreeNode | null>(null);
|
||||
const [breadcrumbs, setBreadcrumbs] = useState<string[]>([]);
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());
|
||||
@@ -275,6 +286,7 @@ const DataBrowser: React.FC = () => {
|
||||
setCurrentNode(null);
|
||||
setBreadcrumbs([]);
|
||||
setExpandedNodes(new Set());
|
||||
setSearchResults(null); // 清空搜索结果
|
||||
} catch (error) {
|
||||
toast({
|
||||
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 isCurrentlyExpanded = expandedNodes.has(node.path);
|
||||
@@ -401,28 +450,12 @@ const DataBrowser: React.FC = () => {
|
||||
onOpen();
|
||||
};
|
||||
|
||||
// 过滤树节点(根据搜索关键词)
|
||||
const filteredTree = useMemo(() => {
|
||||
if (!treeData || !searchQuery) return treeData?.tree || [];
|
||||
|
||||
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;
|
||||
})
|
||||
.filter(Boolean) as TreeNode[];
|
||||
};
|
||||
|
||||
return filterNodes(treeData.tree);
|
||||
// 显示的树节点(搜索时不显示树)
|
||||
const displayTree = useMemo(() => {
|
||||
if (searchQuery.trim()) {
|
||||
return []; // 搜索时不显示树
|
||||
}
|
||||
return treeData?.tree || [];
|
||||
}, [treeData, searchQuery]);
|
||||
|
||||
return (
|
||||
@@ -518,40 +551,64 @@ const DataBrowser: React.FC = () => {
|
||||
mb={6}
|
||||
>
|
||||
<CardBody>
|
||||
<HStack spacing={4}>
|
||||
<Input
|
||||
placeholder="搜索分类或指标名称..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
bg={themeColors.bg.secondary}
|
||||
borderColor={themeColors.border.default}
|
||||
color={themeColors.text.primary}
|
||||
_placeholder={{ color: themeColors.text.muted }}
|
||||
_focus={{
|
||||
borderColor: themeColors.primary.gold,
|
||||
boxShadow: `0 0 0 1px ${themeColors.primary.gold}`,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={<FaSearch />}
|
||||
bg={themeColors.primary.gold}
|
||||
color={themeColors.bg.primary}
|
||||
_hover={{ bg: themeColors.primary.goldLight }}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
{searchQuery && (
|
||||
<VStack spacing={3} align="stretch">
|
||||
<HStack spacing={4}>
|
||||
<Input
|
||||
placeholder="搜索分类或指标名称..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
bg={themeColors.bg.secondary}
|
||||
borderColor={themeColors.border.default}
|
||||
color={themeColors.text.primary}
|
||||
_placeholder={{ color: themeColors.text.muted }}
|
||||
_focus={{
|
||||
borderColor: themeColors.primary.gold,
|
||||
boxShadow: `0 0 0 1px ${themeColors.primary.gold}`,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={<FaTimes />}
|
||||
variant="ghost"
|
||||
color={themeColors.text.secondary}
|
||||
_hover={{ color: themeColors.text.primary }}
|
||||
onClick={() => setSearchQuery('')}
|
||||
leftIcon={<FaSearch />}
|
||||
bg={themeColors.primary.gold}
|
||||
color={themeColors.bg.primary}
|
||||
_hover={{ bg: themeColors.primary.goldLight }}
|
||||
onClick={handleSearch}
|
||||
isLoading={searching}
|
||||
>
|
||||
清除
|
||||
搜索
|
||||
</Button>
|
||||
{searchQuery && (
|
||||
<Button
|
||||
leftIcon={<FaTimes />}
|
||||
variant="ghost"
|
||||
color={themeColors.text.secondary}
|
||||
_hover={{ color: themeColors.text.primary }}
|
||||
onClick={() => setSearchQuery('')}
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</HStack>
|
||||
{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>
|
||||
</Card>
|
||||
</MotionBox>
|
||||
@@ -628,9 +685,77 @@ const DataBrowser: React.FC = () => {
|
||||
<Flex justify="center" align="center" py={10}>
|
||||
<Spinner color={themeColors.primary.gold} size="xl" />
|
||||
</Flex>
|
||||
) : (
|
||||
) : searchQuery.trim() ? (
|
||||
// 搜索模式:显示搜索结果列表
|
||||
<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
|
||||
key={node.path}
|
||||
node={node}
|
||||
@@ -638,7 +763,7 @@ const DataBrowser: React.FC = () => {
|
||||
onNodeClick={handleNodeClick}
|
||||
expandedNodes={expandedNodes}
|
||||
onToggleExpand={toggleNodeExpand}
|
||||
searchQuery={searchQuery}
|
||||
searchQuery=""
|
||||
loadingNodes={loadingNodes}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user