feat: 动态跟踪添加新闻动态二级 Tab

- 添加 Tabs 结构支持二级 Tab 扩展
- Tab1: 新闻动态(复用 NewsEventsTab 组件)
- 实现 loadNewsEvents 数据加载逻辑
- 支持搜索和分页功能
- 自动获取股票名称用于新闻搜索

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-09 18:46:37 +08:00
parent 40f6eaced6
commit 4274341ed5

View File

@@ -1,45 +1,183 @@
// src/views/Company/components/DynamicTracking/index.js // src/views/Company/components/DynamicTracking/index.js
// 动态跟踪 - 独立一级 Tab 组件 // 动态跟踪 - 独立一级 Tab 组件(包含新闻动态等二级 Tab
import React from "react"; import React, { useState, useEffect, useCallback } from "react";
import { import {
Box, Box,
VStack, Tabs,
Text, TabList,
Icon, TabPanels,
Card, Tab,
CardBody, TabPanel,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { FaNewspaper } from "react-icons/fa"; import { FaNewspaper } from "react-icons/fa";
import { logger } from "@utils/logger";
import { getApiBase } from "@utils/apiConfig";
import NewsEventsTab from "../CompanyOverview/NewsEventsTab";
// API配置
const API_BASE_URL = getApiBase();
// 二级 Tab 配置
const TRACKING_TABS = [
{ key: "news", name: "新闻动态", icon: FaNewspaper },
// 后续可扩展更多二级 Tab
];
/** /**
* 动态跟踪组件 * 动态跟踪组件
* *
* 功能: * 功能:
* - 预留二级 Tab 结构 * - 二级 Tab 结构
* - 后续放入新闻动态等 * - Tab1: 新闻动态(复用 NewsEventsTab
* - 预留后续扩展
* *
* @param {Object} props * @param {Object} props
* @param {string} props.stockCode - 股票代码 * @param {string} props.stockCode - 股票代码
*/ */
const DynamicTracking = ({ stockCode }) => { const DynamicTracking = ({ stockCode: propStockCode }) => {
const [stockCode, setStockCode] = useState(propStockCode || "000001");
const [activeTab, setActiveTab] = useState(0);
// 新闻动态状态
const [newsEvents, setNewsEvents] = useState([]);
const [newsLoading, setNewsLoading] = useState(false);
const [newsPagination, setNewsPagination] = useState({
page: 1,
per_page: 10,
total: 0,
pages: 0,
has_next: false,
has_prev: false,
});
const [searchQuery, setSearchQuery] = useState("");
const [stockName, setStockName] = useState("");
const [dataLoaded, setDataLoaded] = useState(false);
// 监听 props 中的 stockCode 变化
useEffect(() => {
if (propStockCode && propStockCode !== stockCode) {
setStockCode(propStockCode);
setDataLoaded(false);
setNewsEvents([]);
setStockName("");
setSearchQuery("");
}
}, [propStockCode, stockCode]);
// 获取股票名称(用于搜索)
const fetchStockName = useCallback(async () => {
try {
const response = await fetch(
`${API_BASE_URL}/api/stock/${stockCode}/basic-info`
);
const result = await response.json();
if (result.success && result.data) {
const name = result.data.SECNAME || result.data.ORGNAME || stockCode;
setStockName(name);
return name;
}
return stockCode;
} catch (err) {
logger.error("DynamicTracking", "fetchStockName", err, { stockCode });
return stockCode;
}
}, [stockCode]);
// 加载新闻事件数据
const loadNewsEvents = useCallback(
async (query, page = 1) => {
setNewsLoading(true);
try {
const searchTerm = query || stockName || stockCode;
const response = await fetch(
`${API_BASE_URL}/api/events?q=${encodeURIComponent(searchTerm)}&page=${page}&per_page=10`
);
const result = await response.json();
if (result.success) {
setNewsEvents(result.data || []);
setNewsPagination({
page: result.pagination?.page || page,
per_page: result.pagination?.per_page || 10,
total: result.pagination?.total || 0,
pages: result.pagination?.pages || 0,
has_next: result.pagination?.has_next || false,
has_prev: result.pagination?.has_prev || false,
});
}
} catch (err) {
logger.error("DynamicTracking", "loadNewsEvents", err, { stockCode });
setNewsEvents([]);
} finally {
setNewsLoading(false);
}
},
[stockCode, stockName]
);
// 首次加载
useEffect(() => {
const initLoad = async () => {
if (stockCode && !dataLoaded) {
const name = await fetchStockName();
await loadNewsEvents(name, 1);
setDataLoaded(true);
}
};
initLoad();
}, [stockCode, dataLoaded, fetchStockName, loadNewsEvents]);
// 搜索处理
const handleSearchChange = (value) => {
setSearchQuery(value);
};
const handleSearch = () => {
loadNewsEvents(searchQuery || stockName, 1);
};
// 分页处理
const handlePageChange = (page) => {
loadNewsEvents(searchQuery || stockName, page);
};
return ( return (
<Card bg="white" shadow="sm"> <Box>
<CardBody> <Tabs
<VStack spacing={4} py={12}> variant="enclosed"
<Icon as={FaNewspaper} boxSize={12} color="gray.300" /> colorScheme="blue"
<Text fontSize="lg" color="gray.500"> index={activeTab}
动态跟踪 onChange={setActiveTab}
</Text> >
<Text fontSize="sm" color="gray.400"> <TabList>
后续将添加新闻动态等内容 {TRACKING_TABS.map((tab) => (
</Text> <Tab key={tab.key} fontWeight="medium">
<Text fontSize="xs" color="gray.300"> {tab.name}
股票代码: {stockCode} </Tab>
</Text> ))}
</VStack> </TabList>
</CardBody>
</Card> <TabPanels>
{/* 新闻动态 Tab */}
<TabPanel p={4}>
<NewsEventsTab
newsEvents={newsEvents}
newsLoading={newsLoading}
newsPagination={newsPagination}
searchQuery={searchQuery}
onSearchChange={handleSearchChange}
onSearch={handleSearch}
onPageChange={handlePageChange}
cardBg="white"
/>
</TabPanel>
{/* 后续可扩展更多 Tab Panel */}
</TabPanels>
</Tabs>
</Box>
); );
}; };