refactor: Company 页面一级 Tab 重构为 6 个
- 新增深度分析 Tab(从 CompanyOverview 提取为独立组件) - 新增动态跟踪 Tab(占位组件,后续添加内容) - Tab 顺序:公司概览 | 深度分析 | 股票行情 | 财务全景 | 盈利预测 | 动态跟踪 - 简化 CompanyOverview:移除内部 Tabs,只保留头部卡片 + 基本信息 - DeepAnalysis 组件独立管理深度分析数据加载(3个接口) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// src/views/Company/components/CompanyOverview/index.js
|
||||
// 公司概览主组件 - 状态管理 + Tab 容器
|
||||
// 公司概览 - 头部卡片 + 基本信息
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
@@ -15,19 +15,12 @@ import {
|
||||
Divider,
|
||||
Spinner,
|
||||
Center,
|
||||
Tabs,
|
||||
TabList,
|
||||
TabPanels,
|
||||
Tab,
|
||||
TabPanel,
|
||||
useColorModeValue,
|
||||
Icon,
|
||||
Grid,
|
||||
GridItem,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
Container,
|
||||
Circle,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
@@ -42,9 +35,6 @@ import {
|
||||
FaEnvelope,
|
||||
FaPhone,
|
||||
FaCrown,
|
||||
FaBrain,
|
||||
FaInfoCircle,
|
||||
FaNewspaper,
|
||||
} from "react-icons/fa";
|
||||
|
||||
import { ExternalLinkIcon } from "@chakra-ui/icons";
|
||||
@@ -53,9 +43,7 @@ import { logger } from "@utils/logger";
|
||||
import { getApiBase } from "@utils/apiConfig";
|
||||
|
||||
// 子组件
|
||||
import DeepAnalysisTab from "./DeepAnalysisTab";
|
||||
import BasicInfoTab from "./BasicInfoTab";
|
||||
import NewsEventsTab from "./NewsEventsTab";
|
||||
|
||||
// API配置
|
||||
const API_BASE_URL = getApiBase();
|
||||
@@ -76,87 +64,47 @@ const formatUtils = {
|
||||
},
|
||||
};
|
||||
|
||||
// 主组件
|
||||
const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
/**
|
||||
* 公司概览组件
|
||||
*
|
||||
* 功能:
|
||||
* - 显示公司头部信息卡片
|
||||
* - 显示基本信息(股权结构、管理层、公告等)
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {string} props.stockCode - 股票代码
|
||||
*/
|
||||
const CompanyOverview = ({ stockCode: propStockCode }) => {
|
||||
const [stockCode, setStockCode] = useState(propStockCode || "000001");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoaded, setDataLoaded] = useState(false);
|
||||
|
||||
// Tab 懒加载状态追踪
|
||||
const [tabsLoaded, setTabsLoaded] = useState({
|
||||
basicInfo: false,
|
||||
deepAnalysis: false,
|
||||
newsEvents: false,
|
||||
});
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
||||
const [basicInfoLoading, setBasicInfoLoading] = useState(false);
|
||||
const [deepAnalysisLoading, setDeepAnalysisLoading] = useState(false);
|
||||
|
||||
// 监听props中的stockCode变化 - 重置Tab状态
|
||||
// 监听 props 中的 stockCode 变化
|
||||
useEffect(() => {
|
||||
if (propStockCode && propStockCode !== stockCode) {
|
||||
setStockCode(propStockCode);
|
||||
// 重置 Tab 状态
|
||||
setTabsLoaded({ basicInfo: false, deepAnalysis: false, newsEvents: false });
|
||||
setActiveTabIndex(0);
|
||||
// 清空深度分析和新闻数据
|
||||
setComprehensiveData(null);
|
||||
setValueChainData(null);
|
||||
setKeyFactorsData(null);
|
||||
setNewsEvents([]);
|
||||
setDataLoaded(false);
|
||||
}
|
||||
}, [propStockCode, stockCode]);
|
||||
|
||||
// 企业深度分析数据
|
||||
const [comprehensiveData, setComprehensiveData] = useState(null);
|
||||
const [valueChainData, setValueChainData] = useState(null);
|
||||
const [keyFactorsData, setKeyFactorsData] = useState(null);
|
||||
|
||||
// 股票概览数据
|
||||
// 基本信息数据
|
||||
const [basicInfo, setBasicInfo] = useState(null);
|
||||
const [actualControl, setActualControl] = useState([]);
|
||||
const [concentration, setConcentration] = useState([]);
|
||||
const [management, setManagement] = useState([]);
|
||||
const [topCirculationShareholders, setTopCirculationShareholders] = useState(
|
||||
[]
|
||||
);
|
||||
const [topCirculationShareholders, setTopCirculationShareholders] = useState([]);
|
||||
const [topShareholders, setTopShareholders] = useState([]);
|
||||
const [branches, setBranches] = useState([]);
|
||||
const [announcements, setAnnouncements] = useState([]);
|
||||
const [disclosureSchedule, setDisclosureSchedule] = useState([]);
|
||||
|
||||
// 新闻动态数据
|
||||
const [newsEvents, setNewsEvents] = useState([]);
|
||||
const [newsLoading, setNewsLoading] = useState(false);
|
||||
const [newsSearchQuery, setNewsSearchQuery] = useState("");
|
||||
const [newsPagination, setNewsPagination] = useState({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
});
|
||||
|
||||
const [_error, setError] = useState(null);
|
||||
|
||||
const bgColor = useColorModeValue("gray.50", "gray.900");
|
||||
const cardBg = useColorModeValue("white", "gray.800");
|
||||
|
||||
// 业务板块详情展开状态
|
||||
const [expandedSegments, setExpandedSegments] = useState({});
|
||||
|
||||
// 切换业务板块展开状态
|
||||
const toggleSegmentExpansion = (segmentIndex) => {
|
||||
setExpandedSegments((prev) => ({
|
||||
...prev,
|
||||
[segmentIndex]: !prev[segmentIndex],
|
||||
}));
|
||||
};
|
||||
|
||||
// 加载基本信息数据(9个接口)- 首次加载
|
||||
// 加载基本信息数据(9个接口)
|
||||
const loadBasicInfoData = async () => {
|
||||
if (tabsLoaded.basicInfo) return;
|
||||
if (dataLoaded) return;
|
||||
|
||||
setBasicInfoLoading(true);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
@@ -202,7 +150,6 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
disclosureRes,
|
||||
] = await Promise.all(requests);
|
||||
|
||||
// 设置股票概览数据
|
||||
if (basicRes.success) setBasicInfo(basicRes.data);
|
||||
if (actualRes.success) setActualControl(actualRes.data);
|
||||
if (concentrationRes.success) setConcentration(concentrationRes.data);
|
||||
@@ -214,166 +161,41 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
if (announcementsRes.success) setAnnouncements(announcementsRes.data);
|
||||
if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data);
|
||||
|
||||
setTabsLoaded((prev) => ({ ...prev, basicInfo: true }));
|
||||
setDataLoaded(true);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
logger.error("CompanyOverview", "loadBasicInfoData", err, { stockCode });
|
||||
} finally {
|
||||
setBasicInfoLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载深度分析数据(3个接口)- Tab切换时加载
|
||||
const loadDeepAnalysisData = async () => {
|
||||
if (tabsLoaded.deepAnalysis) return;
|
||||
|
||||
setDeepAnalysisLoading(true);
|
||||
|
||||
try {
|
||||
const requests = [
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/comprehensive-analysis/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/value-chain-analysis/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/key-factors-timeline/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
];
|
||||
|
||||
const [comprehensiveRes, valueChainRes, keyFactorsRes] =
|
||||
await Promise.all(requests);
|
||||
|
||||
// 设置深度分析数据
|
||||
if (comprehensiveRes.success) setComprehensiveData(comprehensiveRes.data);
|
||||
if (valueChainRes.success) setValueChainData(valueChainRes.data);
|
||||
if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data);
|
||||
|
||||
setTabsLoaded((prev) => ({ ...prev, deepAnalysis: true }));
|
||||
} catch (err) {
|
||||
logger.error("CompanyOverview", "loadDeepAnalysisData", err, {
|
||||
stockCode,
|
||||
});
|
||||
} finally {
|
||||
setDeepAnalysisLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 首次加载 - 只加载基本信息
|
||||
// 首次加载
|
||||
useEffect(() => {
|
||||
if (stockCode) {
|
||||
loadBasicInfoData();
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
// 加载新闻事件(1个接口)- Tab切换时加载
|
||||
const loadNewsEvents = async (page = 1, searchQuery = "") => {
|
||||
setNewsLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
per_page: "10",
|
||||
sort: "new",
|
||||
include_creator: "true",
|
||||
include_stats: "true",
|
||||
});
|
||||
|
||||
const queryText = searchQuery || basicInfo?.SECNAME || "";
|
||||
if (queryText) {
|
||||
params.append("q", queryText);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/api/events?${params.toString()}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
const events = data.data?.events || data.events || [];
|
||||
const pagination = data.data?.pagination || {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
};
|
||||
|
||||
setNewsEvents(events);
|
||||
setNewsPagination(pagination);
|
||||
|
||||
// 首次加载时标记为已加载
|
||||
if (page === 1 && !tabsLoaded.newsEvents) {
|
||||
setTabsLoaded((prev) => ({ ...prev, newsEvents: true }));
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error("CompanyOverview", "loadNewsEvents", err, {
|
||||
stockCode,
|
||||
searchQuery,
|
||||
page,
|
||||
});
|
||||
setNewsEvents([]);
|
||||
setNewsPagination({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
});
|
||||
} finally {
|
||||
setNewsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理新闻搜索
|
||||
const handleNewsSearch = () => {
|
||||
loadNewsEvents(1, newsSearchQuery);
|
||||
};
|
||||
|
||||
// 处理新闻分页
|
||||
const handleNewsPageChange = (newPage) => {
|
||||
loadNewsEvents(newPage, newsSearchQuery);
|
||||
};
|
||||
|
||||
// Tab 切换处理 - 懒加载
|
||||
const handleTabChange = (index) => {
|
||||
setActiveTabIndex(index);
|
||||
// index 0: 基本信息 - 已首次加载
|
||||
// index 1: 深度分析 - 切换时加载
|
||||
// index 2: 新闻动态 - 切换时加载
|
||||
if (index === 1 && !tabsLoaded.deepAnalysis) {
|
||||
loadDeepAnalysisData();
|
||||
} else if (index === 2 && !tabsLoaded.newsEvents && basicInfo) {
|
||||
loadNewsEvents(1);
|
||||
}
|
||||
};
|
||||
|
||||
if (basicInfoLoading && !basicInfo) {
|
||||
if (loading && !basicInfo) {
|
||||
return (
|
||||
<Box bg={bgColor} minH="100vh" p={4}>
|
||||
<Container maxW="container.xl">
|
||||
<Center h="400px">
|
||||
<Center h="300px">
|
||||
<VStack spacing={4}>
|
||||
<Spinner size="xl" color="blue.500" thickness="4px" />
|
||||
<Text>正在加载企业全景数据...</Text>
|
||||
<Text>正在加载公司概览数据...</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box bg={bgColor} minH="100vh" py={6}>
|
||||
<Container maxW="container.xl">
|
||||
<VStack spacing={6} align="stretch">
|
||||
{/* 公司头部信息 - 醒目展示 */}
|
||||
{/* 公司头部信息卡片 */}
|
||||
{basicInfo && (
|
||||
<Card
|
||||
bg={cardBg}
|
||||
shadow="2xl"
|
||||
borderTop="6px solid"
|
||||
bg="white"
|
||||
shadow="lg"
|
||||
borderTop="4px solid"
|
||||
borderTopColor="blue.500"
|
||||
>
|
||||
<CardBody>
|
||||
@@ -381,32 +203,27 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
<GridItem colSpan={{ base: 12, lg: 8 }}>
|
||||
<VStack align="start" spacing={4}>
|
||||
<HStack spacing={4}>
|
||||
<Circle size="70px" bg="blue.500">
|
||||
<Icon as={FaBuilding} color="white" boxSize={10} />
|
||||
<Circle size="60px" bg="blue.500">
|
||||
<Icon as={FaBuilding} color="white" boxSize={8} />
|
||||
</Circle>
|
||||
<VStack align="start" spacing={1}>
|
||||
<HStack>
|
||||
<Heading size="xl" color="blue.600">
|
||||
<Heading size="lg" color="blue.600">
|
||||
{basicInfo.ORGNAME || basicInfo.SECNAME}
|
||||
</Heading>
|
||||
<Badge
|
||||
colorScheme="blue"
|
||||
fontSize="lg"
|
||||
px={3}
|
||||
py={1}
|
||||
>
|
||||
<Badge colorScheme="blue" fontSize="md" px={2} py={1}>
|
||||
{basicInfo.SECCODE}
|
||||
</Badge>
|
||||
</HStack>
|
||||
<HStack spacing={3}>
|
||||
<Badge colorScheme="purple" fontSize="sm">
|
||||
<HStack spacing={2}>
|
||||
<Badge colorScheme="purple" fontSize="xs">
|
||||
{basicInfo.sw_industry_l1}
|
||||
</Badge>
|
||||
<Badge colorScheme="orange" fontSize="sm">
|
||||
<Badge colorScheme="orange" fontSize="xs">
|
||||
{basicInfo.sw_industry_l2}
|
||||
</Badge>
|
||||
{basicInfo.sw_industry_l3 && (
|
||||
<Badge colorScheme="green" fontSize="sm">
|
||||
<Badge colorScheme="green" fontSize="xs">
|
||||
{basicInfo.sw_industry_l3}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -416,58 +233,40 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
|
||||
<Divider />
|
||||
|
||||
<SimpleGrid columns={2} spacing={4} w="full">
|
||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={3} w="full">
|
||||
<HStack>
|
||||
<Icon as={FaUserShield} color="gray.500" />
|
||||
<Icon as={FaUserShield} color="gray.500" boxSize={4} />
|
||||
<Text fontSize="sm">
|
||||
<Text as="span" color="gray.500">
|
||||
法定代表人:
|
||||
</Text>
|
||||
<Text as="span" fontWeight="bold">
|
||||
{basicInfo.legal_representative}
|
||||
</Text>
|
||||
<Text as="span" color="gray.500">法定代表人:</Text>
|
||||
<Text as="span" fontWeight="bold">{basicInfo.legal_representative}</Text>
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack>
|
||||
<Icon as={FaCrown} color="gray.500" />
|
||||
<Icon as={FaCrown} color="gray.500" boxSize={4} />
|
||||
<Text fontSize="sm">
|
||||
<Text as="span" color="gray.500">
|
||||
董事长:
|
||||
</Text>
|
||||
<Text as="span" fontWeight="bold">
|
||||
{basicInfo.chairman}
|
||||
</Text>
|
||||
<Text as="span" color="gray.500">董事长:</Text>
|
||||
<Text as="span" fontWeight="bold">{basicInfo.chairman}</Text>
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack>
|
||||
<Icon as={FaBriefcase} color="gray.500" />
|
||||
<Icon as={FaBriefcase} color="gray.500" boxSize={4} />
|
||||
<Text fontSize="sm">
|
||||
<Text as="span" color="gray.500">
|
||||
总经理:
|
||||
</Text>
|
||||
<Text as="span" fontWeight="bold">
|
||||
{basicInfo.general_manager}
|
||||
</Text>
|
||||
<Text as="span" color="gray.500">总经理:</Text>
|
||||
<Text as="span" fontWeight="bold">{basicInfo.general_manager}</Text>
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack>
|
||||
<Icon as={FaCalendarAlt} color="gray.500" />
|
||||
<Icon as={FaCalendarAlt} color="gray.500" boxSize={4} />
|
||||
<Text fontSize="sm">
|
||||
<Text as="span" color="gray.500">
|
||||
成立日期:
|
||||
</Text>
|
||||
<Text as="span" fontWeight="bold">
|
||||
{formatUtils.formatDate(basicInfo.establish_date)}
|
||||
</Text>
|
||||
<Text as="span" color="gray.500">成立日期:</Text>
|
||||
<Text as="span" fontWeight="bold">{formatUtils.formatDate(basicInfo.establish_date)}</Text>
|
||||
</Text>
|
||||
</HStack>
|
||||
</SimpleGrid>
|
||||
|
||||
<Box>
|
||||
<Text fontSize="sm" color="gray.600" noOfLines={3}>
|
||||
<Text fontSize="sm" color="gray.600" noOfLines={2}>
|
||||
{basicInfo.company_intro}
|
||||
</Text>
|
||||
</Box>
|
||||
</VStack>
|
||||
</GridItem>
|
||||
|
||||
@@ -475,38 +274,30 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
<VStack spacing={3} align="stretch">
|
||||
<Stat>
|
||||
<StatLabel>注册资本</StatLabel>
|
||||
<StatNumber fontSize="3xl" color="blue.500">
|
||||
{formatUtils.formatRegisteredCapital(
|
||||
basicInfo.reg_capital
|
||||
)}
|
||||
<StatNumber fontSize="2xl" color="blue.500">
|
||||
{formatUtils.formatRegisteredCapital(basicInfo.reg_capital)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
|
||||
<Divider />
|
||||
|
||||
<VStack align="stretch" spacing={2}>
|
||||
<VStack align="stretch" spacing={1}>
|
||||
<HStack fontSize="sm">
|
||||
<Icon as={FaMapMarkerAlt} color="gray.500" />
|
||||
<Text noOfLines={1}>
|
||||
{basicInfo.province} {basicInfo.city}
|
||||
</Text>
|
||||
<Icon as={FaMapMarkerAlt} color="gray.500" boxSize={3} />
|
||||
<Text noOfLines={1}>{basicInfo.province} {basicInfo.city}</Text>
|
||||
</HStack>
|
||||
<HStack fontSize="sm">
|
||||
<Icon as={FaGlobe} color="gray.500" />
|
||||
<Link
|
||||
href={basicInfo.website}
|
||||
isExternal
|
||||
color="blue.500"
|
||||
>
|
||||
<Icon as={FaGlobe} color="gray.500" boxSize={3} />
|
||||
<Link href={basicInfo.website} isExternal color="blue.500" noOfLines={1}>
|
||||
{basicInfo.website} <ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
</HStack>
|
||||
<HStack fontSize="sm">
|
||||
<Icon as={FaEnvelope} color="gray.500" />
|
||||
<Text>{basicInfo.email}</Text>
|
||||
<Icon as={FaEnvelope} color="gray.500" boxSize={3} />
|
||||
<Text noOfLines={1}>{basicInfo.email}</Text>
|
||||
</HStack>
|
||||
<HStack fontSize="sm">
|
||||
<Icon as={FaPhone} color="gray.500" />
|
||||
<Icon as={FaPhone} color="gray.500" boxSize={3} />
|
||||
<Text>{basicInfo.tel}</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
@@ -517,38 +308,7 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 主要内容区 - 分为基本信息、深度分析和新闻动态 */}
|
||||
<Tabs
|
||||
variant="soft-rounded"
|
||||
colorScheme="blue"
|
||||
size="lg"
|
||||
index={activeTabIndex}
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
<TabList
|
||||
bg={cardBg}
|
||||
p={4}
|
||||
borderRadius="lg"
|
||||
shadow="md"
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<Tab fontWeight="bold">
|
||||
<Icon as={FaInfoCircle} mr={2} />
|
||||
基本信息
|
||||
</Tab>
|
||||
<Tab fontWeight="bold">
|
||||
<Icon as={FaBrain} mr={2} />
|
||||
深度分析
|
||||
</Tab>
|
||||
<Tab fontWeight="bold">
|
||||
<Icon as={FaNewspaper} mr={2} />
|
||||
新闻动态
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{/* 基本信息标签页 - 默认 Tab */}
|
||||
<TabPanel p={0} pt={6}>
|
||||
{/* 基本信息内容 */}
|
||||
<BasicInfoTab
|
||||
basicInfo={basicInfo}
|
||||
actualControl={actualControl}
|
||||
@@ -559,43 +319,11 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
||||
announcements={announcements}
|
||||
branches={branches}
|
||||
disclosureSchedule={disclosureSchedule}
|
||||
cardBg={cardBg}
|
||||
loading={basicInfoLoading}
|
||||
cardBg="white"
|
||||
loading={loading}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
{/* 深度分析标签页 - 切换时加载 */}
|
||||
<TabPanel p={0} pt={6}>
|
||||
<DeepAnalysisTab
|
||||
comprehensiveData={comprehensiveData}
|
||||
valueChainData={valueChainData}
|
||||
keyFactorsData={keyFactorsData}
|
||||
loading={deepAnalysisLoading}
|
||||
cardBg={cardBg}
|
||||
expandedSegments={expandedSegments}
|
||||
onToggleSegment={toggleSegmentExpansion}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
{/* 新闻动态标签页 - 切换时加载 */}
|
||||
<TabPanel p={0} pt={6}>
|
||||
<NewsEventsTab
|
||||
newsEvents={newsEvents}
|
||||
newsLoading={newsLoading}
|
||||
newsPagination={newsPagination}
|
||||
searchQuery={newsSearchQuery}
|
||||
onSearchChange={setNewsSearchQuery}
|
||||
onSearch={handleNewsSearch}
|
||||
onPageChange={handleNewsPageChange}
|
||||
cardBg={cardBg}
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</VStack>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyAnalysisComplete;
|
||||
export default CompanyOverview;
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
TabPanels,
|
||||
TabPanel,
|
||||
Divider,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import TabNavigation from './TabNavigation';
|
||||
@@ -17,9 +16,11 @@ import { COMPANY_TABS, getTabNameByIndex } from '../../constants';
|
||||
|
||||
// 子组件导入(Tab 内容组件)
|
||||
import CompanyOverview from '../CompanyOverview';
|
||||
import DeepAnalysis from '../DeepAnalysis';
|
||||
import MarketDataView from '../MarketDataView';
|
||||
import FinancialPanorama from '../FinancialPanorama';
|
||||
import ForecastReport from '../ForecastReport';
|
||||
import DynamicTracking from '../DynamicTracking';
|
||||
|
||||
/**
|
||||
* Tab 组件映射
|
||||
@@ -27,9 +28,11 @@ import ForecastReport from '../ForecastReport';
|
||||
*/
|
||||
const TAB_COMPONENTS = {
|
||||
overview: CompanyOverview,
|
||||
analysis: DeepAnalysis,
|
||||
market: MarketDataView,
|
||||
financial: FinancialPanorama,
|
||||
forecast: ForecastReport,
|
||||
tracking: DynamicTracking,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -48,10 +51,6 @@ const TAB_COMPONENTS = {
|
||||
const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
// 主题相关颜色
|
||||
const tabBg = useColorModeValue('gray.50', 'gray.700');
|
||||
const activeBg = useColorModeValue('blue.500', 'blue.400');
|
||||
|
||||
/**
|
||||
* 处理 Tab 切换
|
||||
*/
|
||||
@@ -76,7 +75,7 @@ const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
{/* Tab 导航 */}
|
||||
<TabNavigation tabBg={tabBg} activeBg={activeBg} />
|
||||
<TabNavigation tabBg="gray.50" activeBg="blue.500" />
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
100
src/views/Company/components/DeepAnalysis/index.js
Normal file
100
src/views/Company/components/DeepAnalysis/index.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// src/views/Company/components/DeepAnalysis/index.js
|
||||
// 深度分析 - 独立一级 Tab 组件
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { logger } from "@utils/logger";
|
||||
import { getApiBase } from "@utils/apiConfig";
|
||||
|
||||
// 复用原有的展示组件
|
||||
import DeepAnalysisTab from "../CompanyOverview/DeepAnalysisTab";
|
||||
|
||||
const API_BASE_URL = getApiBase();
|
||||
|
||||
/**
|
||||
* 深度分析组件
|
||||
*
|
||||
* 功能:
|
||||
* - 加载深度分析数据(3个接口)
|
||||
* - 管理展开状态
|
||||
* - 渲染 DeepAnalysisTab 展示组件
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {string} props.stockCode - 股票代码
|
||||
*/
|
||||
const DeepAnalysis = ({ stockCode }) => {
|
||||
// 数据状态
|
||||
const [comprehensiveData, setComprehensiveData] = useState(null);
|
||||
const [valueChainData, setValueChainData] = useState(null);
|
||||
const [keyFactorsData, setKeyFactorsData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 业务板块展开状态
|
||||
const [expandedSegments, setExpandedSegments] = useState({});
|
||||
|
||||
// 切换业务板块展开状态
|
||||
const toggleSegmentExpansion = (segmentIndex) => {
|
||||
setExpandedSegments((prev) => ({
|
||||
...prev,
|
||||
[segmentIndex]: !prev[segmentIndex],
|
||||
}));
|
||||
};
|
||||
|
||||
// 加载深度分析数据(3个接口)
|
||||
const loadDeepAnalysisData = async () => {
|
||||
if (!stockCode) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const requests = [
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/comprehensive-analysis/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/value-chain-analysis/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
fetch(
|
||||
`${API_BASE_URL}/api/company/key-factors-timeline/${stockCode}`
|
||||
).then((r) => r.json()),
|
||||
];
|
||||
|
||||
const [comprehensiveRes, valueChainRes, keyFactorsRes] =
|
||||
await Promise.all(requests);
|
||||
|
||||
if (comprehensiveRes.success) setComprehensiveData(comprehensiveRes.data);
|
||||
if (valueChainRes.success) setValueChainData(valueChainRes.data);
|
||||
if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data);
|
||||
} catch (err) {
|
||||
logger.error("DeepAnalysis", "loadDeepAnalysisData", err, { stockCode });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// stockCode 变更时重新加载数据
|
||||
useEffect(() => {
|
||||
if (stockCode) {
|
||||
// 重置数据
|
||||
setComprehensiveData(null);
|
||||
setValueChainData(null);
|
||||
setKeyFactorsData(null);
|
||||
setExpandedSegments({});
|
||||
// 加载新数据
|
||||
loadDeepAnalysisData();
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
return (
|
||||
<DeepAnalysisTab
|
||||
comprehensiveData={comprehensiveData}
|
||||
valueChainData={valueChainData}
|
||||
keyFactorsData={keyFactorsData}
|
||||
loading={loading}
|
||||
cardBg="white"
|
||||
expandedSegments={expandedSegments}
|
||||
onToggleSegment={toggleSegmentExpansion}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeepAnalysis;
|
||||
46
src/views/Company/components/DynamicTracking/index.js
Normal file
46
src/views/Company/components/DynamicTracking/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
// src/views/Company/components/DynamicTracking/index.js
|
||||
// 动态跟踪 - 独立一级 Tab 组件
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
VStack,
|
||||
Text,
|
||||
Icon,
|
||||
Card,
|
||||
CardBody,
|
||||
} from "@chakra-ui/react";
|
||||
import { FaNewspaper } from "react-icons/fa";
|
||||
|
||||
/**
|
||||
* 动态跟踪组件
|
||||
*
|
||||
* 功能:
|
||||
* - 预留二级 Tab 结构
|
||||
* - 后续放入新闻动态等
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {string} props.stockCode - 股票代码
|
||||
*/
|
||||
const DynamicTracking = ({ stockCode }) => {
|
||||
return (
|
||||
<Card bg="white" shadow="sm">
|
||||
<CardBody>
|
||||
<VStack spacing={4} py={12}>
|
||||
<Icon as={FaNewspaper} boxSize={12} color="gray.300" />
|
||||
<Text fontSize="lg" color="gray.500">
|
||||
动态跟踪
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
后续将添加新闻动态等内容
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.300">
|
||||
股票代码: {stockCode}
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default DynamicTracking;
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/views/Company/constants/index.js
|
||||
// 公司详情页面常量配置
|
||||
|
||||
import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa';
|
||||
import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle, FaBrain, FaNewspaper } from 'react-icons/fa';
|
||||
|
||||
/**
|
||||
* Tab 配置
|
||||
@@ -9,9 +9,11 @@ import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-ic
|
||||
*/
|
||||
export const COMPANY_TABS = [
|
||||
{ key: 'overview', name: '公司概览', icon: FaInfoCircle },
|
||||
{ key: 'analysis', name: '深度分析', icon: FaBrain },
|
||||
{ key: 'market', name: '股票行情', icon: FaChartLine },
|
||||
{ key: 'financial', name: '财务全景', icon: FaMoneyBillWave },
|
||||
{ key: 'forecast', name: '盈利预测', icon: FaChartBar },
|
||||
{ key: 'tracking', name: '动态跟踪', icon: FaNewspaper },
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 公司详情页面入口 - 纯组合层
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Container, VStack, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Container, VStack } from '@chakra-ui/react';
|
||||
|
||||
// 自定义 Hooks
|
||||
import { useCompanyStock } from './hooks/useCompanyStock';
|
||||
@@ -24,8 +24,6 @@ import CompanyTabs from './components/CompanyTabs';
|
||||
* - PostHog 事件追踪
|
||||
*/
|
||||
const CompanyIndex = () => {
|
||||
const bgColor = useColorModeValue('white', 'gray.800');
|
||||
|
||||
// 1. 先获取股票代码(不带追踪回调)
|
||||
const {
|
||||
stockCode,
|
||||
@@ -78,7 +76,7 @@ const CompanyIndex = () => {
|
||||
isInWatchlist={isInWatchlist}
|
||||
isWatchlistLoading={isWatchlistLoading}
|
||||
onWatchlistToggle={handleWatchlistToggle}
|
||||
bgColor={bgColor}
|
||||
bgColor="white"
|
||||
/>
|
||||
|
||||
{/* 股票行情卡片:价格、关键指标、主力动态 */}
|
||||
@@ -88,7 +86,7 @@ const CompanyIndex = () => {
|
||||
<CompanyTabs
|
||||
stockCode={stockCode}
|
||||
onTabChange={trackTabChanged}
|
||||
bgColor={bgColor}
|
||||
bgColor="white"
|
||||
/>
|
||||
</VStack>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user