refactor: 财报披露日程独立为动态跟踪第三个 Tab

- 新建 DisclosureSchedulePanel 组件,独立展示财报披露日程
- 简化 AnnouncementsPanel,移除财报披露日程部分
- DynamicTracking 新增第三个 Tab:财报披露日程
- 更新 mock 数据字段名匹配组件需求

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-10 18:55:30 +08:00
parent c237a4dc0c
commit 2994de98c2
5 changed files with 146 additions and 90 deletions

View File

@@ -180,48 +180,53 @@ export const PINGAN_BANK_DATA = {
announcements: [
{
title: '平安银行股份有限公司2024年第三季度报告',
publish_date: '2024-10-28',
type: '定期报告',
summary: '2024年前三季度实现营业收入1245.6亿元同比增长8.2%净利润402.3亿元同比增长12.5%',
announce_date: '2024-10-28',
info_type: '定期报告',
format: 'PDF',
file_size: 2580,
url: '/announcement/detail/ann_20241028_001'
},
{
title: '关于召开2024年第一次临时股东大会的通知',
publish_date: '2024-10-15',
type: '临时公告',
summary: '定于2024年11月5日召开2024年第一次临时股东大会审议关于调整董事会成员等议案',
announce_date: '2024-10-15',
info_type: '临时公告',
format: 'PDF',
file_size: 156,
url: '/announcement/detail/ann_20241015_001'
},
{
title: '平安银行股份有限公司关于完成注册资本变更登记的公告',
publish_date: '2024-09-20',
type: '临时公告',
summary: '公司已完成注册资本由人民币194.06亿元变更为194.06亿元的工商变更登记手续',
announce_date: '2024-09-20',
info_type: '临时公告',
format: 'PDF',
file_size: 89,
url: '/announcement/detail/ann_20240920_001'
},
{
title: '平安银行股份有限公司2024年半年度报告',
publish_date: '2024-08-28',
type: '定期报告',
summary: '2024年上半年实现营业收入828.5亿元同比增长7.8%净利润265.4亿元同比增长11.2%',
announce_date: '2024-08-28',
info_type: '定期报告',
format: 'PDF',
file_size: 3420,
url: '/announcement/detail/ann_20240828_001'
},
{
title: '关于2024年上半年利润分配预案的公告',
publish_date: '2024-08-20',
type: '分配方案',
summary: '拟以总股本194.06亿股为基数向全体股东每10股派发现金红利2.8元(含税)',
announce_date: '2024-08-20',
info_type: '分配方案',
format: 'PDF',
file_size: 245,
url: '/announcement/detail/ann_20240820_001'
}
],
// 披露时间表
disclosureSchedule: [
{ report_type: '2024年年度报告', planned_date: '2025-04-30', status: '未披露' },
{ report_type: '2024年第四季度报告', planned_date: '2025-01-31', status: '未披露' },
{ report_type: '2024年第三季度报告', planned_date: '2024-10-31', status: '已披露' },
{ report_type: '2024年半年度报告', planned_date: '2024-08-31', status: '已披露' },
{ report_type: '2024年第一季度报告', planned_date: '2024-04-30', status: '已披露' }
{ report_name: '2024年年度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-04-30' },
{ report_name: '2024年第四季度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-01-31' },
{ report_name: '2024年第三季度报告', is_disclosed: true, actual_date: '2024-10-28', latest_scheduled_date: '2024-10-31' },
{ report_name: '2024年半年度报告', is_disclosed: true, actual_date: '2024-08-28', latest_scheduled_date: '2024-08-31' },
{ report_name: '2024年第一季度报告', is_disclosed: true, actual_date: '2024-04-28', latest_scheduled_date: '2024-04-30' }
],
// 综合分析 - 结构与组件期望格式匹配
@@ -1064,14 +1069,14 @@ export const generateCompanyData = (stockCode, stockName = '示例公司') => {
{ name: '广州分公司', address: '广州市天河区某路789号', phone: '020-12345678', type: '分公司', establish_date: '2014-03-20' },
],
announcements: [
{ title: `${stockName}2024年第三季度报告`, publish_date: '2024-10-28', type: '定期报告', summary: '业绩稳步增长', url: '#' },
{ title: `${stockName}2024年半年度报告`, publish_date: '2024-08-28', type: '定期报告', summary: '经营情况良好', url: '#' },
{ title: `关于重大合同签订的公告`, publish_date: '2024-07-15', type: '临时公告', summary: '签订重要销售合同', url: '#' },
{ title: `${stockName}2024年第三季度报告`, announce_date: '2024-10-28', info_type: '定期报告', format: 'PDF', file_size: 1850, url: '#' },
{ title: `${stockName}2024年半年度报告`, announce_date: '2024-08-28', info_type: '定期报告', format: 'PDF', file_size: 2340, url: '#' },
{ title: `关于重大合同签订的公告`, announce_date: '2024-07-15', info_type: '临时公告', format: 'PDF', file_size: 128, url: '#' },
],
disclosureSchedule: [
{ report_type: '2024年年度报告', planned_date: '2025-04-30', status: '未披露' },
{ report_type: '2024年第三季度报告', planned_date: '2024-10-31', status: '已披露' },
{ report_type: '2024年半年度报告', planned_date: '2024-08-31', status: '已披露' },
{ report_name: '2024年年度报告', is_disclosed: false, actual_date: null, latest_scheduled_date: '2025-04-30' },
{ report_name: '2024年第三季度报告', is_disclosed: true, actual_date: '2024-10-28', latest_scheduled_date: '2024-10-31' },
{ report_name: '2024年半年度报告', is_disclosed: true, actual_date: '2024-08-28', latest_scheduled_date: '2024-08-31' },
],
comprehensiveAnalysis: {
qualitative_analysis: {

View File

@@ -11,8 +11,6 @@ import {
Icon,
Card,
CardBody,
SimpleGrid,
Divider,
IconButton,
Button,
Tag,
@@ -25,11 +23,10 @@ import {
ModalFooter,
useDisclosure,
} from "@chakra-ui/react";
import { FaCalendarAlt, FaBullhorn } from "react-icons/fa";
import { FaBullhorn } from "react-icons/fa";
import { ExternalLinkIcon } from "@chakra-ui/icons";
import { useAnnouncementsData } from "../../hooks/useAnnouncementsData";
import { useDisclosureData } from "../../hooks/useDisclosureData";
import { THEME } from "../config";
import { formatDate } from "../utils";
import LoadingState from "./LoadingState";
@@ -39,8 +36,7 @@ interface AnnouncementsPanelProps {
}
const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode }) => {
const { announcements, loading: announcementsLoading } = useAnnouncementsData(stockCode);
const { disclosureSchedule, loading: disclosureLoading } = useDisclosureData(stockCode);
const { announcements, loading } = useAnnouncementsData(stockCode);
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedAnnouncement, setSelectedAnnouncement] = useState<any>(null);
@@ -50,8 +46,6 @@ const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode }) =>
onOpen();
};
const loading = announcementsLoading || disclosureLoading;
if (loading) {
return <LoadingState message="加载公告数据..." />;
}
@@ -59,47 +53,6 @@ const AnnouncementsPanel: React.FC<AnnouncementsPanelProps> = ({ stockCode }) =>
return (
<>
<VStack spacing={4} align="stretch">
{/* 财报披露日程 */}
{disclosureSchedule.length > 0 && (
<Box>
<HStack mb={3}>
<Icon as={FaCalendarAlt} color={THEME.gold} />
<Text fontWeight="bold" color={THEME.textPrimary}></Text>
</HStack>
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={3}>
{disclosureSchedule.slice(0, 4).map((schedule: any, idx: number) => (
<Card
key={idx}
bg={schedule.is_disclosed ? "green.900" : "orange.900"}
border="1px solid"
borderColor={schedule.is_disclosed ? "green.600" : "orange.600"}
size="sm"
>
<CardBody p={3}>
<VStack spacing={1}>
<Badge colorScheme={schedule.is_disclosed ? "green" : "orange"}>
{schedule.report_name}
</Badge>
<Text fontSize="sm" fontWeight="bold" color={THEME.textPrimary}>
{schedule.is_disclosed ? "已披露" : "预计"}
</Text>
<Text fontSize="xs" color={THEME.textSecondary}>
{formatDate(
schedule.is_disclosed
? schedule.actual_date
: schedule.latest_scheduled_date
)}
</Text>
</VStack>
</CardBody>
</Card>
))}
</SimpleGrid>
</Box>
)}
<Divider borderColor={THEME.border} />
{/* 最新公告 */}
<Box>
<HStack mb={3}>

View File

@@ -0,0 +1,83 @@
// src/views/Company/components/CompanyOverview/BasicInfoTab/components/DisclosureSchedulePanel.tsx
// 财报披露日程 Tab Panel
import React from "react";
import {
Box,
VStack,
HStack,
Text,
Badge,
Icon,
Card,
CardBody,
SimpleGrid,
} from "@chakra-ui/react";
import { FaCalendarAlt } from "react-icons/fa";
import { useDisclosureData } from "../../hooks/useDisclosureData";
import { THEME } from "../config";
import { formatDate } from "../utils";
import LoadingState from "./LoadingState";
interface DisclosureSchedulePanelProps {
stockCode: string;
}
const DisclosureSchedulePanel: React.FC<DisclosureSchedulePanelProps> = ({ stockCode }) => {
const { disclosureSchedule, loading } = useDisclosureData(stockCode);
if (loading) {
return <LoadingState message="加载披露日程..." />;
}
if (disclosureSchedule.length === 0) {
return (
<Box textAlign="center" py={8}>
<Text color={THEME.textSecondary}></Text>
</Box>
);
}
return (
<VStack spacing={4} align="stretch">
<Box>
<HStack mb={3}>
<Icon as={FaCalendarAlt} color={THEME.gold} />
<Text fontWeight="bold" color={THEME.textPrimary}></Text>
</HStack>
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={3}>
{disclosureSchedule.map((schedule: any, idx: number) => (
<Card
key={idx}
bg={schedule.is_disclosed ? "green.900" : "orange.900"}
border="1px solid"
borderColor={schedule.is_disclosed ? "green.600" : "orange.600"}
size="sm"
>
<CardBody p={3}>
<VStack spacing={1}>
<Badge colorScheme={schedule.is_disclosed ? "green" : "orange"}>
{schedule.report_name}
</Badge>
<Text fontSize="sm" fontWeight="bold" color={THEME.textPrimary}>
{schedule.is_disclosed ? "已披露" : "预计"}
</Text>
<Text fontSize="xs" color={THEME.textSecondary}>
{formatDate(
schedule.is_disclosed
? schedule.actual_date
: schedule.latest_scheduled_date
)}
</Text>
</VStack>
</CardBody>
</Card>
))}
</SimpleGrid>
</Box>
</VStack>
);
};
export default DisclosureSchedulePanel;

View File

@@ -5,7 +5,6 @@ import { IconType } from "react-icons";
import {
FaShareAlt,
FaUserTie,
FaBullhorn,
FaSitemap,
FaInfoCircle,
} from "react-icons/fa";
@@ -72,12 +71,6 @@ export const TAB_CONFIG: TabConfig[] = [
icon: FaUserTie,
enabled: true,
},
{
key: "announcements",
name: "公司公告",
icon: FaBullhorn,
enabled: true,
},
{
key: "branches",
name: "分支机构",

View File

@@ -10,11 +10,14 @@ import {
Tab,
TabPanel,
} from "@chakra-ui/react";
import { FaNewspaper } from "react-icons/fa";
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();
@@ -22,7 +25,8 @@ const API_BASE_URL = getApiBase();
// 二级 Tab 配置
const TRACKING_TABS = [
{ key: "news", name: "新闻动态", icon: FaNewspaper },
// 后续可扩展更多二级 Tab
{ key: "announcements", name: "公司公告", icon: FaBullhorn },
{ key: "disclosure", name: "财报披露日程", icon: FaCalendarAlt },
];
/**
@@ -144,16 +148,26 @@ const DynamicTracking = ({ stockCode: propStockCode }) => {
};
return (
<Box>
<Box bg={THEME.bg} p={4} borderRadius="md">
<Tabs
variant="enclosed"
colorScheme="blue"
variant="soft-rounded"
index={activeTab}
onChange={setActiveTab}
isLazy
>
<TabList>
<TabList bg={THEME.cardBg} borderBottom="1px solid" borderColor={THEME.border}>
{TRACKING_TABS.map((tab) => (
<Tab key={tab.key} fontWeight="medium">
<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>
))}
@@ -174,7 +188,15 @@ const DynamicTracking = ({ stockCode: propStockCode }) => {
/>
</TabPanel>
{/* 后续可扩展更多 Tab Panel */}
{/* 公司公告 Tab */}
<TabPanel p={4}>
<AnnouncementsPanel stockCode={stockCode} />
</TabPanel>
{/* 财报披露日程 Tab */}
<TabPanel p={4}>
<DisclosureSchedulePanel stockCode={stockCode} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>