// src/views/Company/components/CompanyOverview/index.js
// 公司概览主组件 - 状态管理 + Tab 容器
import React, { useState, useEffect } from "react";
import {
Box,
VStack,
HStack,
Text,
Badge,
Card,
CardBody,
Heading,
SimpleGrid,
Divider,
Spinner,
Center,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
useColorModeValue,
Icon,
Grid,
GridItem,
Stat,
StatLabel,
StatNumber,
Container,
Circle,
Link,
} from "@chakra-ui/react";
import {
FaBuilding,
FaMapMarkerAlt,
FaUserShield,
FaBriefcase,
FaCalendarAlt,
FaGlobe,
FaEnvelope,
FaPhone,
FaCrown,
FaBrain,
FaInfoCircle,
FaNewspaper,
} from "react-icons/fa";
import { ExternalLinkIcon } from "@chakra-ui/icons";
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();
// 格式化工具
const formatUtils = {
formatRegisteredCapital: (value) => {
if (!value && value !== 0) return "-";
const absValue = Math.abs(value);
if (absValue >= 100000) {
return (value / 10000).toFixed(2) + "亿元";
}
return value.toFixed(2) + "万元";
},
formatDate: (dateString) => {
if (!dateString) return "-";
return new Date(dateString).toLocaleDateString("zh-CN");
},
};
// 主组件
const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
const [stockCode, setStockCode] = useState(propStockCode || "000001");
// 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状态
useEffect(() => {
if (propStockCode && propStockCode !== stockCode) {
setStockCode(propStockCode);
// 重置 Tab 状态
setTabsLoaded({ basicInfo: false, deepAnalysis: false, newsEvents: false });
setActiveTabIndex(0);
// 清空深度分析和新闻数据
setComprehensiveData(null);
setValueChainData(null);
setKeyFactorsData(null);
setNewsEvents([]);
}
}, [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 [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个接口)- 首次加载
const loadBasicInfoData = async () => {
if (tabsLoaded.basicInfo) return;
setBasicInfoLoading(true);
setError(null);
try {
const requests = [
fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`).then((r) =>
r.json()
),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/actual-control`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/concentration`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10`
).then((r) => r.json()),
fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`).then((r) =>
r.json()
),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20`
).then((r) => r.json()),
fetch(
`${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule`
).then((r) => r.json()),
];
const [
basicRes,
actualRes,
concentrationRes,
managementRes,
circulationRes,
shareholdersRes,
branchesRes,
announcementsRes,
disclosureRes,
] = await Promise.all(requests);
// 设置股票概览数据
if (basicRes.success) setBasicInfo(basicRes.data);
if (actualRes.success) setActualControl(actualRes.data);
if (concentrationRes.success) setConcentration(concentrationRes.data);
if (managementRes.success) setManagement(managementRes.data);
if (circulationRes.success)
setTopCirculationShareholders(circulationRes.data);
if (shareholdersRes.success) setTopShareholders(shareholdersRes.data);
if (branchesRes.success) setBranches(branchesRes.data);
if (announcementsRes.success) setAnnouncements(announcementsRes.data);
if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data);
setTabsLoaded((prev) => ({ ...prev, basicInfo: true }));
} catch (err) {
setError(err.message);
logger.error("CompanyOverview", "loadBasicInfoData", err, { stockCode });
} finally {
setBasicInfoLoading(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) {
return (
正在加载企业全景数据...
);
}
return (
{/* 公司头部信息 - 醒目展示 */}
{basicInfo && (
{basicInfo.ORGNAME || basicInfo.SECNAME}
{basicInfo.SECCODE}
{basicInfo.sw_industry_l1}
{basicInfo.sw_industry_l2}
{basicInfo.sw_industry_l3 && (
{basicInfo.sw_industry_l3}
)}
法定代表人:
{basicInfo.legal_representative}
董事长:
{basicInfo.chairman}
总经理:
{basicInfo.general_manager}
成立日期:
{formatUtils.formatDate(basicInfo.establish_date)}
{basicInfo.company_intro}
注册资本
{formatUtils.formatRegisteredCapital(
basicInfo.reg_capital
)}
{basicInfo.province} {basicInfo.city}
{basicInfo.website}
{basicInfo.email}
{basicInfo.tel}
)}
{/* 主要内容区 - 分为基本信息、深度分析和新闻动态 */}
基本信息
深度分析
新闻动态
{/* 基本信息标签页 - 默认 Tab */}
{/* 深度分析标签页 - 切换时加载 */}
{/* 新闻动态标签页 - 切换时加载 */}
);
};
export default CompanyAnalysisComplete;