- 新建 DisclosureSchedulePanel 组件,独立展示财报披露日程 - 简化 AnnouncementsPanel,移除财报披露日程部分 - DynamicTracking 新增第三个 Tab:财报披露日程 - 更新 mock 数据字段名匹配组件需求 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
207 lines
6.0 KiB
JavaScript
207 lines
6.0 KiB
JavaScript
// src/views/Company/components/DynamicTracking/index.js
|
||
// 动态跟踪 - 独立一级 Tab 组件(包含新闻动态等二级 Tab)
|
||
|
||
import React, { useState, useEffect, useCallback } from "react";
|
||
import {
|
||
Box,
|
||
Tabs,
|
||
TabList,
|
||
TabPanels,
|
||
Tab,
|
||
TabPanel,
|
||
} from "@chakra-ui/react";
|
||
import { FaNewspaper, FaBullhorn, FaCalendarAlt } from "react-icons/fa";
|
||
|
||
import { logger } from "@utils/logger";
|
||
import { getApiBase } from "@utils/apiConfig";
|
||
import NewsEventsTab from "../CompanyOverview/NewsEventsTab";
|
||
import AnnouncementsPanel from "../CompanyOverview/BasicInfoTab/components/AnnouncementsPanel";
|
||
import DisclosureSchedulePanel from "../CompanyOverview/BasicInfoTab/components/DisclosureSchedulePanel";
|
||
import { THEME } from "../CompanyOverview/BasicInfoTab/config";
|
||
|
||
// API配置
|
||
const API_BASE_URL = getApiBase();
|
||
|
||
// 二级 Tab 配置
|
||
const TRACKING_TABS = [
|
||
{ key: "news", name: "新闻动态", icon: FaNewspaper },
|
||
{ key: "announcements", name: "公司公告", icon: FaBullhorn },
|
||
{ key: "disclosure", name: "财报披露日程", icon: FaCalendarAlt },
|
||
];
|
||
|
||
/**
|
||
* 动态跟踪组件
|
||
*
|
||
* 功能:
|
||
* - 二级 Tab 结构
|
||
* - Tab1: 新闻动态(复用 NewsEventsTab)
|
||
* - 预留后续扩展
|
||
*
|
||
* @param {Object} props
|
||
* @param {string} props.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 (
|
||
<Box bg={THEME.bg} p={4} borderRadius="md">
|
||
<Tabs
|
||
variant="soft-rounded"
|
||
index={activeTab}
|
||
onChange={setActiveTab}
|
||
isLazy
|
||
>
|
||
<TabList bg={THEME.cardBg} borderBottom="1px solid" borderColor={THEME.border}>
|
||
{TRACKING_TABS.map((tab) => (
|
||
<Tab
|
||
key={tab.key}
|
||
fontWeight="medium"
|
||
color={THEME.textSecondary}
|
||
_selected={{
|
||
color: THEME.tabSelected.color,
|
||
bg: THEME.tabSelected.bg,
|
||
borderRadius: "md",
|
||
}}
|
||
_hover={{ color: THEME.gold }}
|
||
>
|
||
{tab.name}
|
||
</Tab>
|
||
))}
|
||
</TabList>
|
||
|
||
<TabPanels>
|
||
{/* 新闻动态 Tab */}
|
||
<TabPanel p={4}>
|
||
<NewsEventsTab
|
||
newsEvents={newsEvents}
|
||
newsLoading={newsLoading}
|
||
newsPagination={newsPagination}
|
||
searchQuery={searchQuery}
|
||
onSearchChange={handleSearchChange}
|
||
onSearch={handleSearch}
|
||
onPageChange={handlePageChange}
|
||
cardBg="white"
|
||
/>
|
||
</TabPanel>
|
||
|
||
{/* 公司公告 Tab */}
|
||
<TabPanel p={4}>
|
||
<AnnouncementsPanel stockCode={stockCode} />
|
||
</TabPanel>
|
||
|
||
{/* 财报披露日程 Tab */}
|
||
<TabPanel p={4}>
|
||
<DisclosureSchedulePanel stockCode={stockCode} />
|
||
</TabPanel>
|
||
</TabPanels>
|
||
</Tabs>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default DynamicTracking;
|