diff --git a/src/views/Company/components/CompanyOverview/BasicInfoTab.js b/src/views/Company/components/CompanyOverview/BasicInfoTab.js
new file mode 100644
index 00000000..2545c029
--- /dev/null
+++ b/src/views/Company/components/CompanyOverview/BasicInfoTab.js
@@ -0,0 +1,941 @@
+// src/views/Company/components/CompanyOverview/BasicInfoTab.js
+// 基本信息 Tab - 股权结构、管理团队、公司公告、分支机构、工商信息
+
+import React from "react";
+import {
+ Box,
+ VStack,
+ HStack,
+ Text,
+ Heading,
+ Badge,
+ Icon,
+ Card,
+ CardBody,
+ CardHeader,
+ SimpleGrid,
+ Avatar,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ TableContainer,
+ Tag,
+ Tooltip,
+ Divider,
+ Center,
+ Code,
+ Tabs,
+ TabList,
+ TabPanels,
+ Tab,
+ TabPanel,
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatHelpText,
+ IconButton,
+ Button,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalCloseButton,
+ ModalBody,
+ ModalFooter,
+ useDisclosure,
+} from "@chakra-ui/react";
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import {
+ FaShareAlt,
+ FaUserTie,
+ FaBullhorn,
+ FaSitemap,
+ FaInfoCircle,
+ FaCrown,
+ FaChartPie,
+ FaUsers,
+ FaChartLine,
+ FaArrowUp,
+ FaArrowDown,
+ FaChartBar,
+ FaBuilding,
+ FaGlobe,
+ FaShieldAlt,
+ FaBriefcase,
+ FaCircle,
+ FaEye,
+ FaVenusMars,
+ FaGraduationCap,
+ FaPassport,
+ FaCalendarAlt,
+} from "react-icons/fa";
+
+// 格式化工具函数
+const formatUtils = {
+ formatPercentage: (value) => {
+ if (value === null || value === undefined) return "-";
+ return `${(value * 100).toFixed(2)}%`;
+ },
+ formatNumber: (value) => {
+ if (value === null || value === undefined) return "-";
+ if (value >= 100000000) {
+ return `${(value / 100000000).toFixed(2)}亿`;
+ } else if (value >= 10000) {
+ return `${(value / 10000).toFixed(2)}万`;
+ }
+ return value.toLocaleString();
+ },
+ formatShares: (value) => {
+ if (value === null || value === undefined) return "-";
+ if (value >= 100000000) {
+ return `${(value / 100000000).toFixed(2)}亿股`;
+ } else if (value >= 10000) {
+ return `${(value / 10000).toFixed(2)}万股`;
+ }
+ return `${value.toLocaleString()}股`;
+ },
+ formatDate: (dateStr) => {
+ if (!dateStr) return "-";
+ return dateStr.split("T")[0];
+ },
+};
+
+// 股东类型标签组件
+const ShareholderTypeBadge = ({ type }) => {
+ const typeConfig = {
+ 基金: { color: "blue", icon: FaChartBar },
+ 个人: { color: "green", icon: FaUserTie },
+ 法人: { color: "purple", icon: FaBuilding },
+ QFII: { color: "orange", icon: FaGlobe },
+ 社保: { color: "red", icon: FaShieldAlt },
+ 保险: { color: "teal", icon: FaShieldAlt },
+ 信托: { color: "cyan", icon: FaBriefcase },
+ 券商: { color: "pink", icon: FaChartLine },
+ };
+
+ const config = Object.entries(typeConfig).find(([key]) =>
+ type?.includes(key)
+ )?.[1] || { color: "gray", icon: FaCircle };
+
+ return (
+
+
+ {type}
+
+ );
+};
+
+/**
+ * 基本信息 Tab 组件
+ *
+ * Props:
+ * - basicInfo: 公司基本信息
+ * - actualControl: 实际控制人数组
+ * - concentration: 股权集中度数组
+ * - topShareholders: 前十大股东数组
+ * - topCirculationShareholders: 前十大流通股东数组
+ * - management: 管理层数组
+ * - announcements: 公告列表数组
+ * - branches: 分支机构数组
+ * - disclosureSchedule: 披露日程数组
+ * - cardBg: 卡片背景色
+ * - onAnnouncementClick: 公告点击回调 (announcement) => void
+ */
+const BasicInfoTab = ({
+ basicInfo,
+ actualControl = [],
+ concentration = [],
+ topShareholders = [],
+ topCirculationShareholders = [],
+ management = [],
+ announcements = [],
+ branches = [],
+ disclosureSchedule = [],
+ cardBg,
+}) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const [selectedAnnouncement, setSelectedAnnouncement] = React.useState(null);
+
+ // 管理层职位分类
+ const getManagementByCategory = () => {
+ const categories = {
+ 高管: [],
+ 董事: [],
+ 监事: [],
+ 其他: [],
+ };
+
+ management.forEach((person) => {
+ if (
+ person.position_category === "高管" ||
+ person.position_name?.includes("总")
+ ) {
+ categories["高管"].push(person);
+ } else if (
+ person.position_category === "董事" ||
+ person.position_name?.includes("董事")
+ ) {
+ categories["董事"].push(person);
+ } else if (
+ person.position_category === "监事" ||
+ person.position_name?.includes("监事")
+ ) {
+ categories["监事"].push(person);
+ } else {
+ categories["其他"].push(person);
+ }
+ });
+
+ return categories;
+ };
+
+ // 计算股权集中度变化
+ const getConcentrationTrend = () => {
+ const grouped = {};
+ concentration.forEach((item) => {
+ if (!grouped[item.end_date]) {
+ grouped[item.end_date] = {};
+ }
+ grouped[item.end_date][item.stat_item] = item;
+ });
+ return Object.entries(grouped)
+ .sort((a, b) => b[0].localeCompare(a[0]))
+ .slice(0, 5);
+ };
+
+ // 处理公告点击
+ const handleAnnouncementClick = (announcement) => {
+ setSelectedAnnouncement(announcement);
+ onOpen();
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ 股权结构
+
+
+
+ 管理团队
+
+
+
+ 公司公告
+
+
+
+ 分支机构
+
+
+
+ 工商信息
+
+
+
+
+ {/* 股权结构标签页 */}
+
+
+ {actualControl.length > 0 && (
+
+
+
+ 实际控制人
+
+
+
+
+
+
+ {actualControl[0].actual_controller_name}
+
+
+
+ {actualControl[0].control_type}
+
+
+ 截至{" "}
+ {formatUtils.formatDate(
+ actualControl[0].end_date
+ )}
+
+
+
+
+ 控制比例
+
+ {formatUtils.formatPercentage(
+ actualControl[0].holding_ratio
+ )}
+
+
+ {formatUtils.formatShares(
+ actualControl[0].holding_shares
+ )}
+
+
+
+
+
+
+ )}
+
+ {concentration.length > 0 && (
+
+
+
+ 股权集中度
+
+
+ {getConcentrationTrend()
+ .slice(0, 1)
+ .map(([date, items]) => (
+
+
+
+ {formatUtils.formatDate(date)}
+
+
+
+
+ {Object.entries(items).map(([key, item]) => (
+
+
+ {item.stat_item}
+
+
+
+ {formatUtils.formatPercentage(
+ item.holding_ratio
+ )}
+
+ {item.ratio_change && (
+ 0
+ ? "red"
+ : "green"
+ }
+ >
+ 0
+ ? FaArrowUp
+ : FaArrowDown
+ }
+ mr={1}
+ boxSize={3}
+ />
+ {Math.abs(
+ item.ratio_change
+ ).toFixed(2)}
+ %
+
+ )}
+
+
+ ))}
+
+
+
+ ))}
+
+
+ )}
+
+ {topShareholders.length > 0 && (
+
+
+
+ 十大股东
+
+ {formatUtils.formatDate(topShareholders[0].end_date)}
+
+
+
+
+
+
+ | 排名 |
+ 股东名称 |
+ 股东类型 |
+ 持股数量 |
+ 持股比例 |
+ 股份性质 |
+
+
+
+ {topShareholders
+ .slice(0, 10)
+ .map((shareholder, idx) => (
+
+ |
+
+ {shareholder.shareholder_rank}
+
+ |
+
+
+
+ {shareholder.shareholder_name}
+
+
+ |
+
+
+ |
+
+ {formatUtils.formatShares(
+ shareholder.holding_shares
+ )}
+ |
+
+
+ {formatUtils.formatPercentage(
+ shareholder.total_share_ratio
+ )}
+
+ |
+
+
+ {shareholder.share_nature || "流通股"}
+
+ |
+
+ ))}
+
+
+
+
+ )}
+
+ {topCirculationShareholders.length > 0 && (
+
+
+
+ 十大流通股东
+
+ {formatUtils.formatDate(
+ topCirculationShareholders[0].end_date
+ )}
+
+
+
+
+
+
+ | 排名 |
+ 股东名称 |
+ 股东类型 |
+ 持股数量 |
+ 流通股比例 |
+
+
+
+ {topCirculationShareholders
+ .slice(0, 10)
+ .map((shareholder, idx) => (
+
+ |
+
+ {shareholder.shareholder_rank}
+
+ |
+
+
+
+ {shareholder.shareholder_name}
+
+
+ |
+
+
+ |
+
+ {formatUtils.formatShares(
+ shareholder.holding_shares
+ )}
+ |
+
+
+ {formatUtils.formatPercentage(
+ shareholder.circulation_share_ratio
+ )}
+
+ |
+
+ ))}
+
+
+
+
+ )}
+
+
+
+ {/* 管理团队标签页 */}
+
+
+ {Object.entries(getManagementByCategory()).map(
+ ([category, people]) =>
+ people.length > 0 && (
+
+
+
+ {category}
+ {people.length}人
+
+
+
+ {people.map((person, idx) => (
+
+
+
+
+
+
+
+ {person.name}
+
+ {person.gender && (
+
+ )}
+
+
+ {person.position_name}
+
+
+ {person.education && (
+
+
+ {person.education}
+
+ )}
+ {person.birth_year && (
+
+ {new Date().getFullYear() -
+ parseInt(person.birth_year)}
+ 岁
+
+ )}
+ {person.nationality &&
+ person.nationality !== "中国" && (
+
+
+ {person.nationality}
+
+ )}
+
+
+ 任职日期:
+ {formatUtils.formatDate(
+ person.start_date
+ )}
+
+
+
+
+
+ ))}
+
+
+ )
+ )}
+
+
+
+ {/* 公司公告标签页 */}
+
+
+ {disclosureSchedule.length > 0 && (
+
+
+
+ 财报披露日程
+
+
+ {disclosureSchedule.slice(0, 4).map((schedule, idx) => (
+
+
+
+
+ {schedule.report_name}
+
+
+ {schedule.is_disclosed ? "已披露" : "预计"}
+
+
+ {formatUtils.formatDate(
+ schedule.is_disclosed
+ ? schedule.actual_date
+ : schedule.latest_scheduled_date
+ )}
+
+
+
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ 最新公告
+
+
+ {announcements.map((announcement, idx) => (
+ handleAnnouncementClick(announcement)}
+ _hover={{ bg: "gray.50" }}
+ >
+
+
+
+
+
+ {announcement.info_type || "公告"}
+
+
+ {formatUtils.formatDate(
+ announcement.announce_date
+ )}
+
+
+
+ {announcement.title}
+
+
+
+ {announcement.format && (
+
+ {announcement.format}
+
+ )}
+ }
+ variant="ghost"
+ onClick={(e) => {
+ e.stopPropagation();
+ window.open(announcement.url, "_blank");
+ }}
+ />
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* 分支机构标签页 */}
+
+ {branches.length > 0 ? (
+
+ {branches.map((branch, idx) => (
+
+
+
+
+
+ {branch.branch_name}
+
+
+ {branch.business_status}
+
+
+
+
+
+
+ 注册资本
+
+
+ {branch.register_capital || "-"}
+
+
+
+
+ 法人代表
+
+
+ {branch.legal_person || "-"}
+
+
+
+
+ 成立日期
+
+
+ {formatUtils.formatDate(branch.register_date)}
+
+
+
+
+ 关联企业
+
+
+ {branch.related_company_count || 0} 家
+
+
+
+
+
+
+ ))}
+
+ ) : (
+
+
+
+ 暂无分支机构信息
+
+
+ )}
+
+
+ {/* 工商信息标签页 */}
+
+ {basicInfo && (
+
+
+
+
+ 工商信息
+
+
+
+
+ 统一信用代码
+
+ {basicInfo.credit_code}
+
+
+
+ 公司规模
+
+ {basicInfo.company_size}
+
+
+
+ 注册地址
+
+
+ {basicInfo.reg_address}
+
+
+
+
+ 办公地址
+
+
+ {basicInfo.office_address}
+
+
+
+
+
+
+
+ 服务机构
+
+
+
+
+ 会计师事务所
+
+
+ {basicInfo.accounting_firm}
+
+
+
+
+ 律师事务所
+
+
+ {basicInfo.law_firm}
+
+
+
+
+
+
+
+
+
+
+ 主营业务
+
+
+ {basicInfo.main_business}
+
+
+
+
+
+ 经营范围
+
+
+ {basicInfo.business_scope}
+
+
+
+ )}
+
+
+
+
+
+
+ {/* 公告详情模态框 */}
+
+
+
+
+
+ {selectedAnnouncement?.title}
+
+
+ {selectedAnnouncement?.info_type || "公告"}
+
+
+ {formatUtils.formatDate(selectedAnnouncement?.announce_date)}
+
+
+
+
+
+
+
+
+ 文件格式:{selectedAnnouncement?.format || "-"}
+
+
+ 文件大小:{selectedAnnouncement?.file_size || "-"} KB
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default BasicInfoTab;
diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js b/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js
new file mode 100644
index 00000000..e39f4c4e
--- /dev/null
+++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js
@@ -0,0 +1,1799 @@
+import React, { useState } from "react";
+import {
+ Box,
+ VStack,
+ HStack,
+ Text,
+ Badge,
+ Card,
+ CardBody,
+ CardHeader,
+ Heading,
+ SimpleGrid,
+ Divider,
+ Center,
+ Alert,
+ AlertIcon,
+ Tabs,
+ TabList,
+ TabPanels,
+ Tab,
+ TabPanel,
+ Button,
+ useColorModeValue,
+ Tag,
+ TagLabel,
+ Icon,
+ Tooltip,
+ Grid,
+ GridItem,
+ useToast,
+ IconButton,
+ Progress,
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatHelpText,
+ Accordion,
+ AccordionItem,
+ AccordionButton,
+ AccordionPanel,
+ AccordionIcon,
+ Fade,
+ ScaleFade,
+ useDisclosure,
+ Modal,
+ ModalOverlay,
+ ModalContent,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ ModalCloseButton,
+ Circle,
+ Spinner,
+} from "@chakra-ui/react";
+
+import {
+ FaBuilding,
+ FaChartLine,
+ FaLightbulb,
+ FaRocket,
+ FaNetworkWired,
+ FaCog,
+ FaTrophy,
+ FaShieldAlt,
+ FaChartPie,
+ FaHistory,
+ FaCheckCircle,
+ FaExclamationCircle,
+ FaArrowUp,
+ FaArrowDown,
+ FaArrowRight,
+ FaArrowLeft,
+ FaStar,
+ FaUserTie,
+ FaIndustry,
+ FaDollarSign,
+ FaBalanceScale,
+ FaFlask,
+ FaHandshake,
+ FaUsers,
+ FaCalendarAlt,
+ FaExpandAlt,
+ FaCompressAlt,
+} from "react-icons/fa";
+
+import { ExternalLinkIcon } from "@chakra-ui/icons";
+import ReactECharts from "echarts-for-react";
+import { logger } from "@utils/logger";
+import { getApiBase } from "@utils/apiConfig";
+
+const API_BASE_URL = getApiBase();
+
+// 格式化工具
+const formatUtils = {
+ formatCurrency: (value) => {
+ if (!value && value !== 0) return "-";
+ const absValue = Math.abs(value);
+ if (absValue >= 100000000) {
+ return (value / 100000000).toFixed(2) + "亿元";
+ } else if (absValue >= 10000) {
+ return (value / 10000).toFixed(2) + "万元";
+ }
+ return value.toFixed(2) + "元";
+ },
+ formatBusinessRevenue: (value, unit) => {
+ if (!value && value !== 0) return "-";
+ if (unit) {
+ if (unit === "元") {
+ const absValue = Math.abs(value);
+ if (absValue >= 100000000) {
+ return (value / 100000000).toFixed(2) + "亿元";
+ } else if (absValue >= 10000) {
+ return (value / 10000).toFixed(2) + "万元";
+ }
+ return value.toFixed(0) + "元";
+ } else if (unit === "万元") {
+ const absValue = Math.abs(value);
+ if (absValue >= 10000) {
+ return (value / 10000).toFixed(2) + "亿元";
+ }
+ return value.toFixed(2) + "万元";
+ } else if (unit === "亿元") {
+ return value.toFixed(2) + "亿元";
+ } else {
+ return value.toFixed(2) + unit;
+ }
+ }
+ const absValue = Math.abs(value);
+ if (absValue >= 100000000) {
+ return (value / 100000000).toFixed(2) + "亿元";
+ } else if (absValue >= 10000) {
+ return (value / 10000).toFixed(2) + "万元";
+ }
+ return value.toFixed(2) + "元";
+ },
+ formatPercentage: (value) => {
+ if (!value && value !== 0) return "-";
+ return value.toFixed(2) + "%";
+ },
+};
+
+// 免责声明组件
+const DisclaimerBox = () => {
+ return (
+
+
+
+
+ 免责声明
+
+
+ 本内容由AI模型基于新闻、公告、研报等公开信息自动分析和生成,未经许可严禁转载。
+ 所有内容仅供参考,不构成任何投资建议,请投资者注意风险,独立审慎决策。
+
+
+
+ );
+};
+
+// 评分进度条组件
+const ScoreBar = ({ label, score, icon }) => {
+ const percentage = (score / 100) * 100;
+ const getColorScheme = () => {
+ if (percentage >= 80) return "purple";
+ if (percentage >= 60) return "blue";
+ if (percentage >= 40) return "yellow";
+ return "orange";
+ };
+
+ return (
+
+
+
+ {icon && (
+
+ )}
+
+ {label}
+
+
+ {score || 0}
+
+
+
+ );
+};
+
+// 业务结构树形图组件
+const BusinessTreeItem = ({ business, depth = 0 }) => {
+ const bgColor = useColorModeValue("gray.50", "gray.700");
+
+ return (
+ 0 ? `4px solid` : "none"}
+ borderLeftColor="blue.400"
+ borderRadius="md"
+ mb={2}
+ _hover={{ shadow: "md" }}
+ transition="all 0.2s"
+ >
+
+
+
+
+ {business.business_name}
+
+ {business.financial_metrics?.revenue_ratio > 30 && (
+
+ 核心业务
+
+ )}
+
+
+
+ 营收占比:{" "}
+ {formatUtils.formatPercentage(
+ business.financial_metrics?.revenue_ratio
+ )}
+
+
+ 毛利率:{" "}
+ {formatUtils.formatPercentage(
+ business.financial_metrics?.gross_margin
+ )}
+
+ {business.growth_metrics?.revenue_growth && (
+ 0 ? "red" : "green"
+ }
+ >
+
+ 增长: {business.growth_metrics.revenue_growth > 0 ? "+" : ""}
+ {formatUtils.formatPercentage(
+ business.growth_metrics.revenue_growth
+ )}
+
+
+ )}
+
+
+
+
+ {(() => {
+ const revenue =
+ business.revenue || business.financial_metrics?.revenue;
+ const unit = business.revenue_unit;
+ if (revenue || revenue === 0) {
+ return formatUtils.formatBusinessRevenue(revenue, unit);
+ }
+ return "-";
+ })()}
+
+
+ 营业收入
+
+
+
+
+ );
+};
+
+// 产业链节点卡片
+const ValueChainNodeCard = ({ node, isCompany = false, level = 0 }) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ const [relatedCompanies, setRelatedCompanies] = useState([]);
+ const [loadingRelated, setLoadingRelated] = useState(false);
+ const toast = useToast();
+
+ const getColorScheme = () => {
+ if (isCompany) return "blue";
+ if (level < 0) return "orange";
+ if (level > 0) return "green";
+ return "gray";
+ };
+
+ const colorScheme = getColorScheme();
+ const bgColor = useColorModeValue(`${colorScheme}.50`, `${colorScheme}.900`);
+ const borderColor = useColorModeValue(
+ `${colorScheme}.200`,
+ `${colorScheme}.600`
+ );
+
+ const getNodeTypeIcon = (type) => {
+ const icons = {
+ company: FaBuilding,
+ supplier: FaHandshake,
+ customer: FaUserTie,
+ product: FaIndustry,
+ service: FaCog,
+ channel: FaNetworkWired,
+ raw_material: FaFlask,
+ };
+ return icons[type] || FaBuilding;
+ };
+
+ const getImportanceColor = (score) => {
+ if (score >= 80) return "red";
+ if (score >= 60) return "orange";
+ if (score >= 40) return "yellow";
+ return "green";
+ };
+
+ const fetchRelatedCompanies = async () => {
+ setLoadingRelated(true);
+ try {
+ const response = await fetch(
+ `${API_BASE_URL}/api/company/value-chain/related-companies?node_name=${encodeURIComponent(
+ node.node_name
+ )}`
+ );
+ const data = await response.json();
+ if (data.success) {
+ setRelatedCompanies(data.data || []);
+ } else {
+ toast({
+ title: "获取相关公司失败",
+ description: data.message,
+ status: "error",
+ duration: 3000,
+ isClosable: true,
+ });
+ }
+ } catch (error) {
+ logger.error("ValueChainNodeCard", "fetchRelatedCompanies", error, {
+ node_name: node.node_name,
+ });
+ toast({
+ title: "获取相关公司失败",
+ description: error.message,
+ status: "error",
+ duration: 3000,
+ isClosable: true,
+ });
+ } finally {
+ setLoadingRelated(false);
+ }
+ };
+
+ const handleCardClick = () => {
+ onOpen();
+ if (relatedCompanies.length === 0) {
+ fetchRelatedCompanies();
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {isCompany && (
+
+ 核心企业
+
+ )}
+
+ {node.importance_score >= 70 && (
+
+
+
+ )}
+
+
+
+ {node.node_name}
+
+
+ {node.node_description && (
+
+ {node.node_description}
+
+ )}
+
+
+
+ {node.node_type}
+
+ {node.market_share && (
+
+ 份额 {node.market_share}%
+
+ )}
+
+
+ {(node.importance_score || node.importance_score === 0) && (
+
+
+
+ 重要度
+
+
+ {node.importance_score}
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {node.node_name}
+
+ {node.node_type}
+ {isCompany && (
+
+ 核心企业
+
+ )}
+
+
+
+
+
+
+
+ {node.node_description && (
+
+
+ 节点描述
+
+
+ {node.node_description}
+
+
+ )}
+
+
+
+ 重要度评分
+
+ {node.importance_score || 0}
+
+
+
+
+
+
+ {node.market_share && (
+
+ 市场份额
+ {node.market_share}%
+
+ )}
+
+ {node.dependency_degree && (
+
+ 依赖程度
+
+ {node.dependency_degree}%
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ 相关公司
+
+ {loadingRelated && }
+
+ {loadingRelated ? (
+
+
+
+ ) : relatedCompanies.length > 0 ? (
+
+ {relatedCompanies.map((company, idx) => {
+ const getLevelLabel = (level) => {
+ if (level < 0) return { text: "上游", color: "orange" };
+ if (level === 0) return { text: "核心", color: "blue" };
+ if (level > 0) return { text: "下游", color: "green" };
+ return { text: "未知", color: "gray" };
+ };
+ const levelInfo = getLevelLabel(
+ company.node_info?.node_level
+ );
+
+ return (
+
+
+
+
+
+
+
+ {company.stock_name}
+
+
+ {company.stock_code}
+
+
+ {levelInfo.text}
+
+
+ {company.company_name && (
+
+ {company.company_name}
+
+ )}
+
+ }
+ variant="ghost"
+ colorScheme="blue"
+ onClick={() => {
+ window.location.href = `/company?stock_code=${company.stock_code}`;
+ }}
+ aria-label="查看公司详情"
+ />
+
+
+ {company.node_info?.node_description && (
+
+ {company.node_info.node_description}
+
+ )}
+
+ {company.relationships &&
+ company.relationships.length > 0 && (
+
+
+ 产业链关系:
+
+
+ {company.relationships.map(
+ (rel, ridx) => (
+
+
+
+ {rel.role === "source"
+ ? "流向"
+ : "来自"}
+
+ {rel.connected_node}
+
+
+
+ )
+ )}
+
+
+ )}
+
+
+
+ );
+ })}
+
+ ) : (
+
+
+
+
+ 暂无相关公司
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+// 关键因素卡片
+const KeyFactorCard = ({ factor }) => {
+ const impactColor =
+ {
+ positive: "red",
+ negative: "green",
+ neutral: "gray",
+ mixed: "yellow",
+ }[factor.impact_direction] || "gray";
+
+ const bgColor = useColorModeValue("white", "gray.800");
+ const borderColor = useColorModeValue("gray.200", "gray.600");
+
+ return (
+
+
+
+
+
+ {factor.factor_name}
+
+
+ {factor.impact_direction === "positive"
+ ? "正面"
+ : factor.impact_direction === "negative"
+ ? "负面"
+ : factor.impact_direction === "mixed"
+ ? "混合"
+ : "中性"}
+
+
+
+
+
+ {factor.factor_value}
+ {factor.factor_unit && ` ${factor.factor_unit}`}
+
+ {factor.year_on_year && (
+ 0 ? "red" : "green"}
+ >
+ 0 ? FaArrowUp : FaArrowDown}
+ mr={1}
+ boxSize={3}
+ />
+ {Math.abs(factor.year_on_year)}%
+
+ )}
+
+
+ {factor.factor_desc && (
+
+ {factor.factor_desc}
+
+ )}
+
+
+
+ 影响权重: {factor.impact_weight}
+
+ {factor.report_period && (
+
+ {factor.report_period}
+
+ )}
+
+
+
+
+ );
+};
+
+// 时间线组件
+const TimelineComponent = ({ events }) => {
+ const [selectedEvent, setSelectedEvent] = useState(null);
+ const { isOpen, onOpen, onClose } = useDisclosure();
+
+ // 颜色模式值需要在组件顶层调用
+ const positiveBgColor = useColorModeValue("red.50", "red.900");
+ const negativeBgColor = useColorModeValue("green.50", "green.900");
+
+ const handleEventClick = (event) => {
+ setSelectedEvent(event);
+ onOpen();
+ };
+
+ return (
+ <>
+
+
+
+
+ {events.map((event, idx) => {
+ const isPositive = event.impact_metrics?.is_positive;
+ const iconColor = isPositive ? "red.500" : "green.500";
+ const bgColor = isPositive ? positiveBgColor : negativeBgColor;
+
+ return (
+
+
+
+
+
+
+
+
+ handleEventClick(event)}
+ _hover={{ shadow: "lg", transform: "translateX(4px)" }}
+ transition="all 0.3s ease"
+ >
+
+
+
+
+
+ {event.event_title}
+
+
+
+
+ {event.event_date}
+
+
+
+
+ {event.event_type}
+
+
+
+
+ {event.event_desc}
+
+
+
+
+ 影响度:
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ {selectedEvent && (
+
+
+
+
+
+
+
+ {selectedEvent.event_title}
+
+
+ {selectedEvent.event_type}
+
+
+ {selectedEvent.event_date}
+
+
+
+
+
+
+
+
+
+
+ 事件详情
+
+
+ {selectedEvent.event_desc}
+
+
+
+ {selectedEvent.related_info?.financial_impact && (
+
+
+ 财务影响
+
+
+ {selectedEvent.related_info.financial_impact}
+
+
+ )}
+
+
+
+ 影响评估
+
+
+
+
+ 影响度
+
+
+
+
+ {selectedEvent.impact_metrics?.is_positive
+ ? "正面影响"
+ : "负面影响"}
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+// 生成雷达图配置
+const getRadarChartOption = (comprehensiveData) => {
+ if (!comprehensiveData?.competitive_position?.scores) return null;
+
+ const scores = comprehensiveData.competitive_position.scores;
+ const indicators = [
+ { name: "市场地位", max: 100 },
+ { name: "技术实力", max: 100 },
+ { name: "品牌价值", max: 100 },
+ { name: "运营效率", max: 100 },
+ { name: "财务健康", max: 100 },
+ { name: "创新能力", max: 100 },
+ { name: "风险控制", max: 100 },
+ { name: "成长潜力", max: 100 },
+ ];
+
+ const data = [
+ scores.market_position || 0,
+ scores.technology || 0,
+ scores.brand || 0,
+ scores.operation || 0,
+ scores.finance || 0,
+ scores.innovation || 0,
+ scores.risk || 0,
+ scores.growth || 0,
+ ];
+
+ return {
+ tooltip: { trigger: "item" },
+ radar: {
+ indicator: indicators,
+ shape: "polygon",
+ splitNumber: 4,
+ name: { textStyle: { color: "#666", fontSize: 12 } },
+ splitLine: {
+ lineStyle: { color: ["#e8e8e8", "#e0e0e0", "#d0d0d0", "#c0c0c0"] },
+ },
+ splitArea: {
+ show: true,
+ areaStyle: {
+ color: ["rgba(250,250,250,0.3)", "rgba(200,200,200,0.3)"],
+ },
+ },
+ axisLine: { lineStyle: { color: "#ddd" } },
+ },
+ series: [
+ {
+ name: "竞争力评分",
+ type: "radar",
+ data: [
+ {
+ value: data,
+ name: "当前评分",
+ symbol: "circle",
+ symbolSize: 5,
+ lineStyle: { width: 2, color: "#3182ce" },
+ areaStyle: { color: "rgba(49, 130, 206, 0.3)" },
+ label: {
+ show: true,
+ formatter: (params) => params.value,
+ color: "#3182ce",
+ fontSize: 10,
+ },
+ },
+ ],
+ },
+ ],
+ };
+};
+
+// 生成桑基图配置
+const getSankeyChartOption = (valueChainData) => {
+ if (
+ !valueChainData?.value_chain_flows ||
+ valueChainData.value_chain_flows.length === 0
+ )
+ return null;
+
+ const nodes = new Set();
+ const links = [];
+
+ valueChainData.value_chain_flows.forEach((flow) => {
+ if (!flow?.source?.node_name || !flow?.target?.node_name) return;
+ nodes.add(flow.source.node_name);
+ nodes.add(flow.target.node_name);
+ links.push({
+ source: flow.source.node_name,
+ target: flow.target.node_name,
+ value: parseFloat(flow.flow_metrics?.flow_ratio) || 1,
+ lineStyle: { color: "source", opacity: 0.6 },
+ });
+ });
+
+ return {
+ tooltip: { trigger: "item", triggerOn: "mousemove" },
+ series: [
+ {
+ type: "sankey",
+ layout: "none",
+ emphasis: { focus: "adjacency" },
+ data: Array.from(nodes).map((name) => ({ name })),
+ links: links,
+ lineStyle: { color: "gradient", curveness: 0.5 },
+ label: { color: "#333", fontSize: 10 },
+ },
+ ],
+ };
+};
+
+// 深度分析 Tab 主组件
+const DeepAnalysisTab = ({
+ comprehensiveData,
+ valueChainData,
+ keyFactorsData,
+ loading,
+ cardBg,
+ expandedSegments,
+ onToggleSegment,
+}) => {
+ const blueBg = useColorModeValue("blue.50", "blue.900");
+ const greenBg = useColorModeValue("green.50", "green.900");
+ const purpleBg = useColorModeValue("purple.50", "purple.900");
+ const orangeBg = useColorModeValue("orange.50", "orange.900");
+
+ if (loading) {
+ return (
+
+
+
+ 加载深度分析数据...
+
+
+ );
+ }
+
+ return (
+
+ {/* 核心定位卡片 */}
+ {comprehensiveData?.qualitative_analysis && (
+
+
+
+
+ 核心定位
+
+
+
+
+
+ {comprehensiveData.qualitative_analysis.core_positioning
+ ?.one_line_intro && (
+
+
+
+ {
+ comprehensiveData.qualitative_analysis.core_positioning
+ .one_line_intro
+ }
+
+
+ )}
+
+
+
+
+
+ 投资亮点
+
+
+
+ {comprehensiveData.qualitative_analysis.core_positioning
+ ?.investment_highlights || "暂无数据"}
+
+
+
+
+
+
+
+
+ 商业模式
+
+
+
+ {comprehensiveData.qualitative_analysis.core_positioning
+ ?.business_model_desc || "暂无数据"}
+
+
+
+
+
+
+
+
+ )}
+
+ {/* 竞争地位分析 */}
+ {comprehensiveData?.competitive_position && (
+
+
+
+
+ 竞争地位分析
+ {comprehensiveData.competitive_position.ranking && (
+
+ 行业排名{" "}
+ {comprehensiveData.competitive_position.ranking.industry_rank}
+ /
+ {
+ comprehensiveData.competitive_position.ranking
+ .total_companies
+ }
+
+ )}
+
+
+
+
+ {comprehensiveData.competitive_position.analysis
+ ?.main_competitors && (
+
+
+ 主要竞争对手
+
+
+ {comprehensiveData.competitive_position.analysis.main_competitors
+ .split(",")
+ .map((competitor, idx) => (
+
+
+ {competitor.trim()}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {getRadarChartOption(comprehensiveData) && (
+
+ )}
+
+
+
+
+
+
+
+
+ 竞争优势
+
+
+ {comprehensiveData.competitive_position.analysis
+ ?.competitive_advantages || "暂无数据"}
+
+
+
+
+ 竞争劣势
+
+
+ {comprehensiveData.competitive_position.analysis
+ ?.competitive_disadvantages || "暂无数据"}
+
+
+
+
+
+ )}
+
+ {/* 业务结构分析 */}
+ {comprehensiveData?.business_structure &&
+ comprehensiveData.business_structure.length > 0 && (
+
+
+
+
+ 业务结构分析
+
+ {comprehensiveData.business_structure[0]?.report_period}
+
+
+
+
+
+
+ {comprehensiveData.business_structure.map((business, idx) => (
+
+ ))}
+
+
+
+ )}
+
+ {/* 产业链分析 */}
+ {valueChainData && (
+
+
+
+
+ 产业链分析
+
+
+ 上游 {valueChainData.analysis_summary?.upstream_nodes || 0}
+
+
+ 核心 {valueChainData.analysis_summary?.company_nodes || 0}
+
+
+ 下游 {valueChainData.analysis_summary?.downstream_nodes || 0}
+
+
+
+
+
+
+
+
+ 层级视图
+ 流向关系
+
+
+
+
+
+ {(valueChainData.value_chain_structure?.nodes_by_level?.[
+ "level_-2"
+ ] ||
+ valueChainData.value_chain_structure?.nodes_by_level?.[
+ "level_-1"
+ ]) && (
+
+
+
+ 上游供应链
+
+
+ 原材料与供应商
+
+
+
+ {[
+ ...(valueChainData.value_chain_structure
+ ?.nodes_by_level?.["level_-2"] || []),
+ ...(valueChainData.value_chain_structure
+ ?.nodes_by_level?.["level_-1"] || []),
+ ].map((node, idx) => (
+
+ ))}
+
+
+ )}
+
+ {valueChainData.value_chain_structure?.nodes_by_level?.[
+ "level_0"
+ ] && (
+
+
+
+ 核心企业
+
+
+ 公司主体与产品
+
+
+
+ {valueChainData.value_chain_structure.nodes_by_level[
+ "level_0"
+ ].map((node, idx) => (
+
+ ))}
+
+
+ )}
+
+ {(valueChainData.value_chain_structure?.nodes_by_level?.[
+ "level_1"
+ ] ||
+ valueChainData.value_chain_structure?.nodes_by_level?.[
+ "level_2"
+ ]) && (
+
+
+
+ 下游客户
+
+
+ 客户与终端市场
+
+
+
+ {[
+ ...(valueChainData.value_chain_structure
+ ?.nodes_by_level?.["level_1"] || []),
+ ...(valueChainData.value_chain_structure
+ ?.nodes_by_level?.["level_2"] || []),
+ ].map((node, idx) => (
+
+ ))}
+
+
+ )}
+
+
+
+
+ {getSankeyChartOption(valueChainData) ? (
+
+ ) : (
+
+ 暂无流向数据
+
+ )}
+
+
+
+
+
+ )}
+
+ {/* 关键因素与发展时间线 */}
+
+
+ {keyFactorsData?.key_factors && (
+
+
+
+
+ 关键因素
+ {keyFactorsData.key_factors.total_factors} 项
+
+
+
+
+
+ {keyFactorsData.key_factors.categories.map(
+ (category, idx) => (
+
+
+
+
+
+ {category.category_name}
+
+
+ {category.factors.length}
+
+
+
+
+
+
+
+ {category.factors.map((factor, fidx) => (
+
+ ))}
+
+
+
+ )
+ )}
+
+
+
+ )}
+
+
+
+ {keyFactorsData?.development_timeline && (
+
+
+
+
+ 发展时间线
+
+
+ 正面{" "}
+ {keyFactorsData.development_timeline.statistics
+ ?.positive_events || 0}
+
+
+ 负面{" "}
+ {keyFactorsData.development_timeline.statistics
+ ?.negative_events || 0}
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {/* 业务板块详情 */}
+ {comprehensiveData?.business_segments &&
+ comprehensiveData.business_segments.length > 0 && (
+
+
+
+
+ 业务板块详情
+
+ {comprehensiveData.business_segments.length} 个板块
+
+
+
+
+
+
+ {comprehensiveData.business_segments.map((segment, idx) => {
+ const isExpanded = expandedSegments[idx];
+
+ return (
+
+
+
+
+
+ {segment.segment_name}
+
+
+ }
+ onClick={() => onToggleSegment(idx)}
+ colorScheme="blue"
+ >
+ {isExpanded ? "折叠" : "展开"}
+
+
+
+
+
+ 业务描述
+
+
+ {segment.segment_description || "暂无描述"}
+
+
+
+
+
+ 竞争地位
+
+
+ {segment.competitive_position || "暂无数据"}
+
+
+
+
+
+ 未来潜力
+
+
+ {segment.future_potential || "暂无数据"}
+
+
+
+ {isExpanded && segment.key_products && (
+
+
+ 主要产品
+
+
+ {segment.key_products}
+
+
+ )}
+
+ {isExpanded && segment.market_share && (
+
+
+ 市场份额
+
+
+ {segment.market_share}%
+
+
+ )}
+
+ {isExpanded && segment.revenue_contribution && (
+
+
+ 营收贡献
+
+
+ {segment.revenue_contribution}%
+
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+ {/* 战略分析 */}
+ {comprehensiveData?.qualitative_analysis?.strategy && (
+
+
+
+
+ 战略分析
+
+
+
+
+
+
+
+
+ 战略方向
+
+
+
+ {comprehensiveData.qualitative_analysis.strategy
+ .strategy_description || "暂无数据"}
+
+
+
+
+
+
+
+
+ 战略举措
+
+
+
+ {comprehensiveData.qualitative_analysis.strategy
+ .strategic_initiatives || "暂无数据"}
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default DeepAnalysisTab;
diff --git a/src/views/Company/components/CompanyOverview/NewsEventsTab.js b/src/views/Company/components/CompanyOverview/NewsEventsTab.js
new file mode 100644
index 00000000..52bb4b5f
--- /dev/null
+++ b/src/views/Company/components/CompanyOverview/NewsEventsTab.js
@@ -0,0 +1,541 @@
+// src/views/Company/components/CompanyOverview/NewsEventsTab.js
+// 新闻动态 Tab - 相关新闻事件列表 + 分页
+
+import React from "react";
+import {
+ Box,
+ VStack,
+ HStack,
+ Text,
+ Badge,
+ Icon,
+ Card,
+ CardBody,
+ Button,
+ Input,
+ InputGroup,
+ InputLeftElement,
+ Tag,
+ Center,
+ Spinner,
+ useColorModeValue,
+} from "@chakra-ui/react";
+import { SearchIcon } from "@chakra-ui/icons";
+import {
+ FaNewspaper,
+ FaBullhorn,
+ FaGavel,
+ FaFlask,
+ FaDollarSign,
+ FaShieldAlt,
+ FaFileAlt,
+ FaIndustry,
+ FaEye,
+ FaFire,
+ FaChartLine,
+ FaChevronLeft,
+ FaChevronRight,
+} from "react-icons/fa";
+
+/**
+ * 新闻动态 Tab 组件
+ *
+ * Props:
+ * - newsEvents: 新闻事件列表数组
+ * - newsLoading: 加载状态
+ * - newsPagination: 分页信息 { page, per_page, total, pages, has_next, has_prev }
+ * - searchQuery: 搜索关键词
+ * - onSearchChange: 搜索输入回调 (value) => void
+ * - onSearch: 搜索提交回调 () => void
+ * - onPageChange: 分页回调 (page) => void
+ * - cardBg: 卡片背景色
+ */
+const NewsEventsTab = ({
+ newsEvents = [],
+ newsLoading = false,
+ newsPagination = {
+ page: 1,
+ per_page: 10,
+ total: 0,
+ pages: 0,
+ has_next: false,
+ has_prev: false,
+ },
+ searchQuery = "",
+ onSearchChange,
+ onSearch,
+ onPageChange,
+ cardBg,
+}) => {
+ // 颜色模式值需要在组件顶层调用
+ const hoverBg = useColorModeValue("gray.50", "gray.700");
+
+ // 事件类型图标映射
+ const getEventTypeIcon = (eventType) => {
+ const iconMap = {
+ 企业公告: FaBullhorn,
+ 政策: FaGavel,
+ 技术突破: FaFlask,
+ 企业融资: FaDollarSign,
+ 政策监管: FaShieldAlt,
+ 政策动态: FaFileAlt,
+ 行业事件: FaIndustry,
+ };
+ return iconMap[eventType] || FaNewspaper;
+ };
+
+ // 重要性颜色映射
+ const getImportanceColor = (importance) => {
+ const colorMap = {
+ S: "red",
+ A: "orange",
+ B: "yellow",
+ C: "green",
+ };
+ return colorMap[importance] || "gray";
+ };
+
+ // 处理搜索输入
+ const handleInputChange = (e) => {
+ onSearchChange?.(e.target.value);
+ };
+
+ // 处理搜索提交
+ const handleSearchSubmit = () => {
+ onSearch?.();
+ };
+
+ // 处理键盘事件
+ const handleKeyPress = (e) => {
+ if (e.key === "Enter") {
+ handleSearchSubmit();
+ }
+ };
+
+ // 处理分页
+ const handlePageChange = (page) => {
+ onPageChange?.(page);
+ // 滚动到列表顶部
+ document
+ .getElementById("news-list-top")
+ ?.scrollIntoView({ behavior: "smooth" });
+ };
+
+ // 渲染分页按钮
+ const renderPaginationButtons = () => {
+ const { page: currentPage, pages: totalPages } = newsPagination;
+ const pageButtons = [];
+
+ // 显示当前页及前后各2页
+ let startPage = Math.max(1, currentPage - 2);
+ let endPage = Math.min(totalPages, currentPage + 2);
+
+ // 如果开始页大于1,显示省略号
+ if (startPage > 1) {
+ pageButtons.push(
+
+ ...
+
+ );
+ }
+
+ for (let i = startPage; i <= endPage; i++) {
+ pageButtons.push(
+
+ );
+ }
+
+ // 如果结束页小于总页数,显示省略号
+ if (endPage < totalPages) {
+ pageButtons.push(
+
+ ...
+
+ );
+ }
+
+ return pageButtons;
+ };
+
+ return (
+
+
+
+
+ {/* 搜索框和统计信息 */}
+
+
+
+
+
+
+
+
+
+
+
+ {newsPagination.total > 0 && (
+
+
+
+ 共找到{" "}
+
+ {newsPagination.total}
+ {" "}
+ 条新闻
+
+
+ )}
+
+
+
+
+ {/* 新闻列表 */}
+ {newsLoading ? (
+
+
+
+ 正在加载新闻...
+
+
+ ) : newsEvents.length > 0 ? (
+ <>
+
+ {newsEvents.map((event, idx) => {
+ const importanceColor = getImportanceColor(
+ event.importance
+ );
+ const eventTypeIcon = getEventTypeIcon(event.event_type);
+
+ return (
+
+
+
+ {/* 标题栏 */}
+
+
+
+
+
+ {event.title}
+
+
+
+ {/* 标签栏 */}
+
+ {event.importance && (
+
+ {event.importance}级
+
+ )}
+ {event.event_type && (
+
+ {event.event_type}
+
+ )}
+ {event.invest_score && (
+
+ 投资分: {event.invest_score}
+
+ )}
+ {event.keywords && event.keywords.length > 0 && (
+ <>
+ {event.keywords
+ .slice(0, 4)
+ .map((keyword, kidx) => (
+
+ {typeof keyword === "string"
+ ? keyword
+ : keyword?.concept ||
+ keyword?.name ||
+ "未知"}
+
+ ))}
+ >
+ )}
+
+
+
+ {/* 右侧信息栏 */}
+
+
+ {event.created_at
+ ? new Date(
+ event.created_at
+ ).toLocaleDateString("zh-CN", {
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ })
+ : ""}
+
+
+ {event.view_count !== undefined && (
+
+
+
+ {event.view_count}
+
+
+ )}
+ {event.hot_score !== undefined && (
+
+
+
+ {event.hot_score.toFixed(1)}
+
+
+ )}
+
+ {event.creator && (
+
+ @{event.creator.username}
+
+ )}
+
+
+
+ {/* 描述 */}
+ {event.description && (
+
+ {event.description}
+
+ )}
+
+ {/* 收益率数据 */}
+ {(event.related_avg_chg !== null ||
+ event.related_max_chg !== null ||
+ event.related_week_chg !== null) && (
+
+
+
+
+
+ 相关涨跌:
+
+
+ {event.related_avg_chg !== null &&
+ event.related_avg_chg !== undefined && (
+
+
+ 平均
+
+ 0
+ ? "red.500"
+ : "green.500"
+ }
+ >
+ {event.related_avg_chg > 0 ? "+" : ""}
+ {event.related_avg_chg.toFixed(2)}%
+
+
+ )}
+ {event.related_max_chg !== null &&
+ event.related_max_chg !== undefined && (
+
+
+ 最大
+
+ 0
+ ? "red.500"
+ : "green.500"
+ }
+ >
+ {event.related_max_chg > 0 ? "+" : ""}
+ {event.related_max_chg.toFixed(2)}%
+
+
+ )}
+ {event.related_week_chg !== null &&
+ event.related_week_chg !== undefined && (
+
+
+ 周
+
+ 0
+ ? "red.500"
+ : "green.500"
+ }
+ >
+ {event.related_week_chg > 0
+ ? "+"
+ : ""}
+ {event.related_week_chg.toFixed(2)}%
+
+
+ )}
+
+
+ )}
+
+
+
+ );
+ })}
+
+
+ {/* 分页控件 */}
+ {newsPagination.pages > 1 && (
+
+
+ {/* 分页信息 */}
+
+ 第 {newsPagination.page} / {newsPagination.pages} 页
+
+
+ {/* 分页按钮 */}
+
+
+
+
+ {/* 页码按钮 */}
+ {renderPaginationButtons()}
+
+
+
+
+
+
+ )}
+ >
+ ) : (
+
+
+
+
+ 暂无相关新闻
+
+
+ {searchQuery ? "尝试修改搜索关键词" : "该公司暂无新闻动态"}
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default NewsEventsTab;
diff --git a/src/views/Company/components/CompanyOverview/index.js b/src/views/Company/components/CompanyOverview/index.js
index 13318f96..7929a8bc 100644
--- a/src/views/Company/components/CompanyOverview/index.js
+++ b/src/views/Company/components/CompanyOverview/index.js
@@ -1,872 +1,84 @@
-import React, { useState, useEffect } from 'react';
+// src/views/Company/components/CompanyOverview/index.js
+// 公司概览主组件 - 状态管理 + Tab 容器
+
+import React, { useState, useEffect } from "react";
import {
- Box, VStack, HStack, Text, Badge, Card, CardBody, CardHeader,
- Heading, SimpleGrid, Divider, Spinner, Center, Alert, AlertIcon,
- Tabs, TabList, TabPanels, Tab, TabPanel, Button, useColorModeValue,
- Tag, TagLabel, Icon, Tooltip, Flex, Grid, GridItem, useToast,
- Table, Thead, Tbody, Tr, Th, Td, TableContainer, IconButton,
- Skeleton, SkeletonText, Progress, Stack, Stat, StatLabel, StatNumber,
- StatHelpText, Container, Wrap, WrapItem, List, ListItem,
- ListIcon, Accordion, AccordionItem, AccordionButton, AccordionPanel,
- AccordionIcon, Fade, ScaleFade, useDisclosure, Modal, ModalOverlay,
- ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton,
- Circle, Square, Avatar, AvatarGroup, Input, InputGroup, InputLeftElement,
- Link, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Image, Code,
- chakra
-} from '@chakra-ui/react';
+ 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, FaChartLine, FaLightbulb, FaRocket,
- FaNetworkWired, FaChevronDown, FaChevronUp, FaChevronLeft, FaChevronRight,
- FaCog, FaTrophy, FaShieldAlt, FaBrain, FaChartPie, FaHistory, FaCheckCircle,
- FaExclamationCircle, FaArrowUp, FaArrowDown, FaArrowRight, FaArrowLeft,
- FaLink, FaStar, FaUserTie, FaIndustry, FaDollarSign, FaBalanceScale, FaChartBar,
- FaEye, FaFlask, FaHandshake, FaUsers, FaClock, FaCalendarAlt,
- FaCircle, FaGlobe, FaEnvelope, FaPhone, FaFax, FaBriefcase,
- FaUniversity, FaGraduationCap, FaVenusMars, FaPassport, FaFileAlt,
- FaNewspaper, FaBullhorn, FaUserShield, FaShareAlt, FaSitemap,
- FaSearch, FaDownload, FaExternalLinkAlt, FaInfoCircle, FaCrown,
- FaCertificate, FaAward, FaExpandAlt, FaCompressAlt, FaGavel, FaFire
-} from 'react-icons/fa';
+ FaBuilding,
+ FaMapMarkerAlt,
+ FaUserShield,
+ FaBriefcase,
+ FaCalendarAlt,
+ FaGlobe,
+ FaEnvelope,
+ FaPhone,
+ FaCrown,
+ FaBrain,
+ FaInfoCircle,
+ FaNewspaper,
+} from "react-icons/fa";
-import {
- RepeatIcon, InfoIcon, ChevronRightIcon, TimeIcon, EmailIcon,
- PhoneIcon, ExternalLinkIcon, AttachmentIcon, CalendarIcon, SearchIcon,
- WarningIcon, CheckIcon
-} from '@chakra-ui/icons';
+import { ExternalLinkIcon } from "@chakra-ui/icons";
-import ReactECharts from 'echarts-for-react';
-import { logger } from '@utils/logger';
-import { getApiBase } from '@utils/apiConfig';
+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 = {
- formatCurrency: (value) => {
- if (!value && value !== 0) return '-';
- const absValue = Math.abs(value);
- if (absValue >= 100000000) {
- return (value / 100000000).toFixed(2) + '亿元';
- } else if (absValue >= 10000) {
- return (value / 10000).toFixed(2) + '万元';
- }
- return value.toFixed(2) + '元';
- },
formatRegisteredCapital: (value) => {
- // 注册资本字段,数据库存储的是万元为单位的数值
- if (!value && value !== 0) return '-';
+ if (!value && value !== 0) return "-";
const absValue = Math.abs(value);
- if (absValue >= 100000) { // 10亿万元 = 10亿元
- return (value / 10000).toFixed(2) + '亿元';
+ if (absValue >= 100000) {
+ return (value / 10000).toFixed(2) + "亿元";
}
- return value.toFixed(2) + '万元';
- },
- formatBusinessRevenue: (value, unit) => {
- // 业务收入格式化,考虑数据库中的单位字段
- if (!value && value !== 0) return '-';
-
- if (unit) {
- // 根据数据库中的单位进行智能格式化
- if (unit === '元') {
- // 元为单位时,自动转换为合适的单位显示
- const absValue = Math.abs(value);
- if (absValue >= 100000000) {
- return (value / 100000000).toFixed(2) + '亿元';
- } else if (absValue >= 10000) {
- return (value / 10000).toFixed(2) + '万元';
- }
- return value.toFixed(0) + '元';
- } else if (unit === '万元') {
- // 万元为单位时,可能需要转换为亿元
- const absValue = Math.abs(value);
- if (absValue >= 10000) {
- return (value / 10000).toFixed(2) + '亿元';
- }
- return value.toFixed(2) + '万元';
- } else if (unit === '亿元') {
- // 亿元为单位时,直接显示
- return value.toFixed(2) + '亿元';
- } else {
- // 其他单位直接显示
- return value.toFixed(2) + unit;
- }
- }
-
- // 没有单位字段时,使用默认的货币格式化
- const absValue = Math.abs(value);
- if (absValue >= 100000000) {
- return (value / 100000000).toFixed(2) + '亿元';
- } else if (absValue >= 10000) {
- return (value / 10000).toFixed(2) + '万元';
- }
- return value.toFixed(2) + '元';
- },
- formatPercentage: (value) => {
- if (!value && value !== 0) return '-';
- return value.toFixed(2) + '%';
- },
- formatNumber: (value) => {
- if (!value && value !== 0) return '-';
- return value.toLocaleString('zh-CN');
+ return value.toFixed(2) + "万元";
},
formatDate: (dateString) => {
- if (!dateString) return '-';
- return new Date(dateString).toLocaleDateString('zh-CN');
+ if (!dateString) return "-";
+ return new Date(dateString).toLocaleDateString("zh-CN");
},
- formatShares: (value) => {
- if (!value && value !== 0) return '-';
- const absValue = Math.abs(value);
- if (absValue >= 100000000) {
- return (value / 100000000).toFixed(2) + '亿股';
- } else if (absValue >= 10000) {
- return (value / 10000).toFixed(2) + '万股';
- }
- return value.toFixed(0) + '股';
- }
};
-// 免责声明组件
-const DisclaimerBox = () => {
- return (
-
-
-
- 免责声明
-
- 本内容由AI模型基于新闻、公告、研报等公开信息自动分析和生成,未经许可严禁转载。
- 所有内容仅供参考,不构成任何投资建议,请投资者注意风险,独立审慎决策。
-
-
-
- );
-};
-
-// 评分进度条组件
-const ScoreBar = ({ label, score, maxScore = 100, colorScheme = 'blue', icon }) => {
- const percentage = (score / maxScore) * 100;
- const getColorScheme = () => {
- if (percentage >= 80) return 'purple';
- if (percentage >= 60) return 'blue';
- if (percentage >= 40) return 'yellow';
- return 'orange';
- };
-
- return (
-
-
-
- {icon && }
- {label}
-
- {score || 0}
-
-
-
- );
-};
-
-// 业务结构树形图组件
-const BusinessTreeItem = ({ business, depth = 0 }) => {
- const bgColor = useColorModeValue('gray.50', 'gray.700');
- const borderColor = useColorModeValue('gray.200', 'gray.600');
- const profitColor = business.financial_metrics?.profit_growth > 0 ? 'red.500' : 'green.500';
-
- return (
- 0 ? `4px solid` : 'none'}
- borderLeftColor="blue.400"
- borderRadius="md"
- mb={2}
- _hover={{ shadow: 'md' }}
- transition="all 0.2s"
- >
-
-
-
-
- {business.business_name}
-
- {business.financial_metrics?.revenue_ratio > 30 && (
- 核心业务
- )}
-
-
-
- 营收占比: {formatUtils.formatPercentage(business.financial_metrics?.revenue_ratio)}
-
-
- 毛利率: {formatUtils.formatPercentage(business.financial_metrics?.gross_margin)}
-
- {business.growth_metrics?.revenue_growth && (
- 0 ? 'red' : 'green'}>
-
- 增长: {business.growth_metrics.revenue_growth > 0 ? '+' : ''}{formatUtils.formatPercentage(business.growth_metrics.revenue_growth)}
-
-
- )}
-
-
-
-
- {(() => {
- // 优先使用business.revenue,如果没有则使用financial_metrics.revenue
- const revenue = business.revenue || business.financial_metrics?.revenue;
- const unit = business.revenue_unit;
- if (revenue || revenue === 0) {
- return formatUtils.formatBusinessRevenue(revenue, unit);
- }
- return '-';
- })()}
-
- 营业收入
-
-
-
- );
-};
-
-// 产业链节点卡片
-const ValueChainNodeCard = ({ node, isCompany = false, level = 0 }) => {
- const { isOpen, onOpen, onClose } = useDisclosure();
- const [relatedCompanies, setRelatedCompanies] = useState([]);
- const [loadingRelated, setLoadingRelated] = useState(false);
- const toast = useToast();
-
- const getColorScheme = () => {
- if (isCompany) return 'blue';
- if (level < 0) return 'orange';
- if (level > 0) return 'green';
- return 'gray';
- };
-
- const colorScheme = getColorScheme();
- const bgColor = useColorModeValue(`${colorScheme}.50`, `${colorScheme}.900`);
- const borderColor = useColorModeValue(`${colorScheme}.200`, `${colorScheme}.600`);
-
- const getNodeTypeIcon = (type) => {
- const icons = {
- 'company': FaBuilding,
- 'supplier': FaHandshake,
- 'customer': FaUserTie,
- 'product': FaIndustry,
- 'service': FaCog,
- 'channel': FaNetworkWired,
- 'raw_material': FaFlask
- };
- return icons[type] || FaBuilding;
- };
-
- const getImportanceColor = (score) => {
- if (score >= 80) return 'red';
- if (score >= 60) return 'orange';
- if (score >= 40) return 'yellow';
- return 'green';
- };
-
- // 获取相关公司
- const fetchRelatedCompanies = async () => {
- setLoadingRelated(true);
- try {
- const response = await fetch(
- `${API_BASE_URL}/api/company/value-chain/related-companies?node_name=${encodeURIComponent(node.node_name)}`
- );
- const data = await response.json();
- if (data.success) {
- setRelatedCompanies(data.data || []);
- } else {
- toast({
- title: '获取相关公司失败',
- description: data.message,
- status: 'error',
- duration: 3000,
- isClosable: true,
- });
- }
- } catch (error) {
- logger.error('ValueChainNodeCard', 'fetchRelatedCompanies', error, { node_name: node.node_name });
- toast({
- title: '获取相关公司失败',
- description: error.message,
- status: 'error',
- duration: 3000,
- isClosable: true,
- });
- } finally {
- setLoadingRelated(false);
- }
- };
-
- const handleCardClick = () => {
- onOpen();
- if (relatedCompanies.length === 0) {
- fetchRelatedCompanies();
- }
- };
-
- return (
- <>
-
-
-
-
-
-
-
- {isCompany && (
- 核心企业
- )}
-
- {node.importance_score >= 70 && (
-
-
-
- )}
-
-
-
- {node.node_name}
-
-
- {node.node_description && (
-
- {node.node_description}
-
- )}
-
-
-
- {node.node_type}
-
- {node.market_share && (
-
- 份额 {node.market_share}%
-
- )}
-
-
- {(node.importance_score || node.importance_score === 0) && (
-
-
- 重要度
- {node.importance_score}
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
- {node.node_name}
-
- {node.node_type}
- {isCompany && 核心企业}
-
-
-
-
-
-
-
- {node.node_description && (
-
- 节点描述
- {node.node_description}
-
- )}
-
-
-
- 重要度评分
- {node.importance_score || 0}
-
-
-
-
-
- {node.market_share && (
-
- 市场份额
- {node.market_share}%
-
- )}
-
- {node.dependency_degree && (
-
- 依赖程度
- {node.dependency_degree}%
-
-
-
- )}
-
-
-
-
- {/* 相关公司列表 */}
-
-
- 相关公司
- {loadingRelated && }
-
- {loadingRelated ? (
-
-
-
- ) : relatedCompanies.length > 0 ? (
-
- {relatedCompanies.map((company, idx) => {
- // 获取节点层级标签
- const getLevelLabel = (level) => {
- if (level < 0) return { text: '上游', color: 'orange' };
- if (level === 0) return { text: '核心', color: 'blue' };
- if (level > 0) return { text: '下游', color: 'green' };
- return { text: '未知', color: 'gray' };
- };
-
- const levelInfo = getLevelLabel(company.node_info?.node_level);
-
- return (
-
-
-
- {/* 公司基本信息 */}
-
-
-
- {company.stock_name}
- {company.stock_code}
-
- {levelInfo.text}
-
- {company.node_info?.node_type && (
-
- {company.node_info.node_type}
-
- )}
-
- {company.company_name && (
-
- {company.company_name}
-
- )}
-
- }
- variant="ghost"
- colorScheme="blue"
- onClick={() => {
- window.location.href = `/company?stock_code=${company.stock_code}`;
- }}
- aria-label="查看公司详情"
- />
-
-
- {/* 节点描述 */}
- {company.node_info?.node_description && (
-
- {company.node_info.node_description}
-
- )}
-
- {/* 节点指标 */}
- {(company.node_info?.importance_score || company.node_info?.market_share || company.node_info?.dependency_degree) && (
-
- {company.node_info.importance_score && (
-
- 重要度:
- {company.node_info.importance_score}
-
- )}
- {company.node_info.market_share && (
-
- 市场份额:
- {company.node_info.market_share}%
-
- )}
- {company.node_info.dependency_degree && (
-
- 依赖度:
- {company.node_info.dependency_degree}%
-
- )}
-
- )}
-
- {/* 流向关系 */}
- {company.relationships && company.relationships.length > 0 && (
-
-
- 产业链关系:
-
-
- {company.relationships.map((rel, ridx) => (
-
-
-
- {rel.role === 'source' ? '流向' : '来自'}
-
- {rel.connected_node}
-
-
- {rel.relationship_desc && (
-
- {rel.relationship_desc}
-
- )}
- {rel.flow_ratio && (
-
- {rel.flow_ratio}%
-
- )}
-
- ))}
-
-
- )}
-
-
-
- );
- })}
-
- ) : (
-
-
-
- 暂无相关公司
-
-
- )}
-
-
-
-
-
-
-
-
- >
- );
-};
-
-// 关键因素卡片
-const KeyFactorCard = ({ factor }) => {
- const impactColor = {
- positive: 'red',
- negative: 'green',
- neutral: 'gray',
- mixed: 'yellow'
- }[factor.impact_direction] || 'gray';
-
- const bgColor = useColorModeValue('white', 'gray.800');
- const borderColor = useColorModeValue('gray.200', 'gray.600');
-
- return (
-
-
-
-
- {factor.factor_name}
-
- {factor.impact_direction === 'positive' ? '正面' :
- factor.impact_direction === 'negative' ? '负面' :
- factor.impact_direction === 'mixed' ? '混合' : '中性'}
-
-
-
-
-
- {factor.factor_value}
- {factor.factor_unit && ` ${factor.factor_unit}`}
-
- {factor.year_on_year && (
- 0 ? 'red' : 'green'}>
- 0 ? FaArrowUp : FaArrowDown} mr={1} boxSize={3} />
- {Math.abs(factor.year_on_year)}%
-
- )}
-
-
- {factor.factor_desc && (
-
- {factor.factor_desc}
-
- )}
-
-
-
- 影响权重: {factor.impact_weight}
-
- {factor.report_period && (
- {factor.report_period}
- )}
-
-
-
-
- );
-};
-
-// 时间线组件
-const TimelineComponent = ({ events }) => {
- const [selectedEvent, setSelectedEvent] = useState(null);
- const { isOpen, onOpen, onClose } = useDisclosure();
-
- const handleEventClick = (event) => {
- setSelectedEvent(event);
- onOpen();
- };
-
- return (
- <>
-
-
-
-
- {events.map((event, idx) => {
- const isPositive = event.impact_metrics?.is_positive;
- const iconColor = isPositive ? 'red.500' : 'green.500';
- const bgColor = useColorModeValue(
- isPositive ? 'red.50' : 'green.50',
- isPositive ? 'red.900' : 'green.900'
- );
-
- return (
-
-
-
-
-
-
-
-
- handleEventClick(event)}
- _hover={{ shadow: 'lg', transform: 'translateX(4px)' }}
- transition="all 0.3s ease"
- >
-
-
-
-
- {event.event_title}
-
-
-
- {event.event_date}
-
-
-
-
- {event.event_type}
-
-
-
-
- {event.event_desc}
-
-
-
- 影响度:
-
-
-
-
-
-
- );
- })}
-
-
-
- {selectedEvent && (
-
-
-
-
-
-
-
- {selectedEvent.event_title}
-
-
- {selectedEvent.event_type}
-
- {selectedEvent.event_date}
-
-
-
-
-
-
-
-
- 事件详情
- {selectedEvent.event_desc}
-
-
- {selectedEvent.related_info?.financial_impact && (
-
- 财务影响
-
- {selectedEvent.related_info.financial_impact}
-
-
- )}
-
-
- 影响评估
-
-
- 影响度
-
-
-
- {selectedEvent.impact_metrics?.is_positive ? '正面影响' : '负面影响'}
-
-
-
-
-
-
-
-
-
-
-
- )}
- >
- );
-};
-
-// 股东类型标签组件
-const ShareholderTypeBadge = ({ type }) => {
- const typeConfig = {
- '基金': { color: 'blue', icon: FaChartBar },
- '个人': { color: 'green', icon: FaUserTie },
- '法人': { color: 'purple', icon: FaBuilding },
- 'QFII': { color: 'orange', icon: FaGlobe },
- '社保': { color: 'red', icon: FaShieldAlt },
- '保险': { color: 'teal', icon: FaShieldAlt },
- '信托': { color: 'cyan', icon: FaBriefcase },
- '券商': { color: 'pink', icon: FaChartLine }
- };
-
- const config = Object.entries(typeConfig).find(([key]) => type?.includes(key))?.[1] ||
- { color: 'gray', icon: FaCircle };
-
- return (
-
-
- {type}
-
- );
-};
-
-// 主组件 - 完整版
+// 主组件
const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
- const [stockCode, setStockCode] = useState(propStockCode || '000001');
+ const [stockCode, setStockCode] = useState(propStockCode || "000001");
const [loading, setLoading] = useState(false);
// 监听props中的stockCode变化
@@ -875,18 +87,20 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
setStockCode(propStockCode);
}
}, [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([]);
@@ -895,97 +109,113 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
// 新闻动态数据
const [newsEvents, setNewsEvents] = useState([]);
const [newsLoading, setNewsLoading] = useState(false);
- const [newsSearchQuery, setNewsSearchQuery] = useState('');
+ const [newsSearchQuery, setNewsSearchQuery] = useState("");
const [newsPagination, setNewsPagination] = useState({
page: 1,
per_page: 10,
total: 0,
pages: 0,
has_next: false,
- has_prev: false
+ has_prev: false,
});
- const [error, setError] = useState(null);
-
- const toast = useToast();
- const bgColor = useColorModeValue('gray.50', 'gray.900');
- const cardBg = useColorModeValue('white', 'gray.800');
- // 高亮区域颜色(修复:不能在 JSX 中调用 hooks)
- const blueBg = useColorModeValue('blue.50', 'blue.900');
- const greenBg = useColorModeValue('green.50', 'green.900');
- const purpleBg = useColorModeValue('purple.50', 'purple.900');
- const orangeBg = useColorModeValue('orange.50', 'orange.900');
- const { isOpen: isAnnouncementOpen, onOpen: onAnnouncementOpen, onClose: onAnnouncementClose } = useDisclosure();
- const [selectedAnnouncement, setSelectedAnnouncement] = useState(null);
-
+ 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 => ({
+ setExpandedSegments((prev) => ({
...prev,
- [segmentIndex]: !prev[segmentIndex]
+ [segmentIndex]: !prev[segmentIndex],
}));
};
-
+
// 加载数据
const loadData = async () => {
setLoading(true);
setError(null);
-
+
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()),
+ 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()),
// 股票概览数据
- 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())
+ 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 [
- comprehensiveRes, valueChainRes, keyFactorsRes,
- basicRes, actualRes, concentrationRes, managementRes,
- circulationRes, shareholdersRes, branchesRes, announcementsRes, disclosureRes
+ comprehensiveRes,
+ valueChainRes,
+ keyFactorsRes,
+ basicRes,
+ actualRes,
+ concentrationRes,
+ managementRes,
+ circulationRes,
+ shareholdersRes,
+ branchesRes,
+ announcementsRes,
+ disclosureRes,
] = await Promise.all(requests);
// 设置深度分析数据
if (comprehensiveRes.success) setComprehensiveData(comprehensiveRes.data);
if (valueChainRes.success) setValueChainData(valueChainRes.data);
if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data);
-
+
// 设置股票概览数据
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 (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);
-
} catch (err) {
setError(err.message);
- logger.error('CompanyOverview', 'loadData', err, { stockCode });
-
- // ❌ 移除数据加载失败toast
- // toast({
- // title: '数据加载失败',
- // description: err.message,
- // status: 'error',
- // duration: 3000,
- // isClosable: true,
- // });
+ logger.error("CompanyOverview", "loadData", err, { stockCode });
} finally {
setLoading(false);
}
@@ -998,30 +228,27 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
}, [stockCode]);
// 加载新闻事件
- const loadNewsEvents = async (page = 1, searchQuery = '') => {
+ 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'
+ per_page: "10",
+ sort: "new",
+ include_creator: "true",
+ include_stats: "true",
});
- // 搜索关键词优先级:
- // 1. 用户输入的搜索关键词
- // 2. 股票简称
- const queryText = searchQuery || basicInfo?.SECNAME || '';
+ const queryText = searchQuery || basicInfo?.SECNAME || "";
if (queryText) {
- params.append('q', queryText);
+ params.append("q", queryText);
}
- const response = await fetch(`${API_BASE_URL}/api/events?${params.toString()}`);
+ const response = await fetch(
+ `${API_BASE_URL}/api/events?${params.toString()}`
+ );
const data = await response.json();
- // API返回 data.data.events
const events = data.data?.events || data.events || [];
const pagination = data.data?.pagination || {
page: 1,
@@ -1029,13 +256,17 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
total: 0,
pages: 0,
has_next: false,
- has_prev: false
+ has_prev: false,
};
setNewsEvents(events);
setNewsPagination(pagination);
} catch (err) {
- logger.error('CompanyOverview', 'loadNewsEvents', err, { stockCode, searchQuery, page });
+ logger.error("CompanyOverview", "loadNewsEvents", err, {
+ stockCode,
+ searchQuery,
+ page,
+ });
setNewsEvents([]);
setNewsPagination({
page: 1,
@@ -1043,7 +274,7 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
total: 0,
pages: 0,
has_next: false,
- has_prev: false
+ has_prev: false,
});
} finally {
setNewsLoading(false);
@@ -1057,159 +288,14 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
}
}, [basicInfo]);
- // 处理搜索
+ // 处理新闻搜索
const handleNewsSearch = () => {
loadNewsEvents(1, newsSearchQuery);
};
- // 处理分页
+ // 处理新闻分页
const handleNewsPageChange = (newPage) => {
loadNewsEvents(newPage, newsSearchQuery);
- // 滚动到新闻列表顶部
- document.getElementById('news-list-top')?.scrollIntoView({ behavior: 'smooth' });
- };
-
- // 管理层职位分类
- const getManagementByCategory = () => {
- const categories = {
- '高管': [],
- '董事': [],
- '监事': [],
- '其他': []
- };
-
- management.forEach(person => {
- if (person.position_category === '高管' || person.position_name?.includes('总')) {
- categories['高管'].push(person);
- } else if (person.position_category === '董事' || person.position_name?.includes('董事')) {
- categories['董事'].push(person);
- } else if (person.position_category === '监事' || person.position_name?.includes('监事')) {
- categories['监事'].push(person);
- } else {
- categories['其他'].push(person);
- }
- });
-
- return categories;
- };
-
- // 计算股权集中度变化
- const getConcentrationTrend = () => {
- const grouped = {};
- concentration.forEach(item => {
- if (!grouped[item.end_date]) {
- grouped[item.end_date] = {};
- }
- grouped[item.end_date][item.stat_item] = item;
- });
- return Object.entries(grouped).sort((a, b) => b[0].localeCompare(a[0])).slice(0, 5);
- };
-
- // 生成雷达图配置
- const getRadarChartOption = () => {
- if (!comprehensiveData?.competitive_position?.scores) return null;
-
- const scores = comprehensiveData.competitive_position.scores;
- const indicators = [
- { name: '市场地位', max: 100 },
- { name: '技术实力', max: 100 },
- { name: '品牌价值', max: 100 },
- { name: '运营效率', max: 100 },
- { name: '财务健康', max: 100 },
- { name: '创新能力', max: 100 },
- { name: '风险控制', max: 100 },
- { name: '成长潜力', max: 100 }
- ];
-
- const data = [
- scores.market_position || 0,
- scores.technology || 0,
- scores.brand || 0,
- scores.operation || 0,
- scores.finance || 0,
- scores.innovation || 0,
- scores.risk || 0,
- scores.growth || 0
- ];
-
- return {
- tooltip: { trigger: 'item' },
- radar: {
- indicator: indicators,
- shape: 'polygon',
- splitNumber: 4,
- name: {
- textStyle: { color: '#666', fontSize: 12 }
- },
- splitLine: {
- lineStyle: {
- color: ['#e8e8e8', '#e0e0e0', '#d0d0d0', '#c0c0c0']
- }
- },
- splitArea: {
- show: true,
- areaStyle: {
- color: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)']
- }
- },
- axisLine: {
- lineStyle: { color: '#ddd' }
- }
- },
- series: [{
- name: '竞争力评分',
- type: 'radar',
- data: [{
- value: data,
- name: '当前评分',
- symbol: 'circle',
- symbolSize: 5,
- lineStyle: { width: 2, color: '#3182ce' },
- areaStyle: { color: 'rgba(49, 130, 206, 0.3)' },
- label: {
- show: true,
- formatter: (params) => params.value,
- color: '#3182ce',
- fontSize: 10
- }
- }]
- }]
- };
- };
-
- // 生成产业链桑基图配置
- const getSankeyChartOption = () => {
- if (!valueChainData?.value_chain_flows || valueChainData.value_chain_flows.length === 0) return null;
-
- const nodes = new Set();
- const links = [];
-
- valueChainData.value_chain_flows.forEach(flow => {
- // 检查 source 和 target 是否存在
- if (!flow?.source?.node_name || !flow?.target?.node_name) return;
-
- nodes.add(flow.source.node_name);
- nodes.add(flow.target.node_name);
- links.push({
- source: flow.source.node_name,
- target: flow.target.node_name,
- value: parseFloat(flow.flow_metrics?.flow_ratio) || 1,
- lineStyle: { color: 'source', opacity: 0.6 }
- });
- });
-
- return {
- tooltip: { trigger: 'item', triggerOn: 'mousemove' },
- series: [{
- type: 'sankey',
- layout: 'none',
- emphasis: { focus: 'adjacency' },
- data: Array.from(nodes).map(name => ({ name })),
- links: links,
- lineStyle: { color: 'gradient', curveness: 0.5 },
- label: { color: '#333', fontSize: 10 }
- }]
- };
};
if (loading) {
@@ -1231,10 +317,14 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
-
{/* 公司头部信息 - 醒目展示 */}
{basicInfo && (
-
+
@@ -1248,7 +338,12 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
{basicInfo.ORGNAME || basicInfo.SECNAME}
-
+
{basicInfo.SECCODE}
@@ -1267,40 +362,56 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
-
+
-
+
- 法定代表人:
- {basicInfo.legal_representative}
+
+ 法定代表人:
+
+
+ {basicInfo.legal_representative}
+
- 董事长:
- {basicInfo.chairman}
+
+ 董事长:
+
+
+ {basicInfo.chairman}
+
- 总经理:
- {basicInfo.general_manager}
+
+ 总经理:
+
+
+ {basicInfo.general_manager}
+
- 成立日期:
- {formatUtils.formatDate(basicInfo.establish_date)}
+
+ 成立日期:
+
+
+ {formatUtils.formatDate(basicInfo.establish_date)}
+
-
+
{basicInfo.company_intro}
@@ -1308,26 +419,34 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
-
+
注册资本
- {formatUtils.formatRegisteredCapital(basicInfo.reg_capital)}
+ {formatUtils.formatRegisteredCapital(
+ basicInfo.reg_capital
+ )}
-
+
-
+
- {basicInfo.province} {basicInfo.city}
+
+ {basicInfo.province} {basicInfo.city}
+
-
+
{basicInfo.website}
@@ -1348,1333 +467,80 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
)}
{/* 主要内容区 - 分为深度分析、基本信息和新闻动态 */}
-
-
- 深度分析
- 基本信息
- 新闻动态
+
+
+
+
+ 深度分析
+
+
+
+ 基本信息
+
+
+
+ 新闻动态
+
{/* 深度分析标签页 */}
-
- {/* 核心定位卡片 */}
- {comprehensiveData?.qualitative_analysis && (
-
-
-
-
- 核心定位
-
-
-
-
-
- {comprehensiveData.qualitative_analysis.core_positioning?.one_line_intro && (
-
-
- {comprehensiveData.qualitative_analysis.core_positioning.one_line_intro}
-
- )}
-
-
-
-
- 投资亮点
-
-
- {comprehensiveData.qualitative_analysis.core_positioning?.investment_highlights || '暂无数据'}
-
-
-
-
-
-
-
- 商业模式
-
-
- {comprehensiveData.qualitative_analysis.core_positioning?.business_model_desc || '暂无数据'}
-
-
-
-
-
-
-
-
- )}
-
- {/* 竞争地位分析 */}
- {comprehensiveData?.competitive_position && (
-
-
-
-
- 竞争地位分析
- {comprehensiveData.competitive_position.ranking && (
-
- 行业排名 {comprehensiveData.competitive_position.ranking.industry_rank}/{comprehensiveData.competitive_position.ranking.total_companies}
-
- )}
-
-
-
-
- {comprehensiveData.competitive_position.analysis?.main_competitors && (
-
- 主要竞争对手
-
- {comprehensiveData.competitive_position.analysis.main_competitors
- .split(',')
- .map((competitor, idx) => (
-
-
- {competitor.trim()}
-
- ))}
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {getRadarChartOption() && (
-
- )}
-
-
-
-
-
-
-
- 竞争优势
-
- {comprehensiveData.competitive_position.analysis?.competitive_advantages || '暂无数据'}
-
-
-
- 竞争劣势
-
- {comprehensiveData.competitive_position.analysis?.competitive_disadvantages || '暂无数据'}
-
-
-
-
-
- )}
-
- {/* 业务结构分析 */}
- {comprehensiveData?.business_structure && comprehensiveData.business_structure.length > 0 && (
-
-
-
-
- 业务结构分析
- {comprehensiveData.business_structure[0]?.report_period}
-
-
-
-
-
- {comprehensiveData.business_structure.map((business, idx) => (
-
- ))}
-
-
-
- )}
-
- {/* 产业链分析 */}
- {valueChainData && (
-
-
-
-
- 产业链分析
-
-
- 上游 {valueChainData.analysis_summary?.upstream_nodes || 0}
-
-
- 核心 {valueChainData.analysis_summary?.company_nodes || 0}
-
-
- 下游 {valueChainData.analysis_summary?.downstream_nodes || 0}
-
-
-
-
-
-
-
-
- 层级视图
- 流向关系
-
-
-
-
-
- {(valueChainData.value_chain_structure?.nodes_by_level?.['level_-2'] ||
- valueChainData.value_chain_structure?.nodes_by_level?.['level_-1']) && (
-
-
- 上游供应链
- 原材料与供应商
-
-
- {[
- ...(valueChainData.value_chain_structure?.nodes_by_level?.['level_-2'] || []),
- ...(valueChainData.value_chain_structure?.nodes_by_level?.['level_-1'] || [])
- ].map((node, idx) => (
-
- ))}
-
-
- )}
-
- {valueChainData.value_chain_structure?.nodes_by_level?.['level_0'] && (
-
-
- 核心企业
- 公司主体与产品
-
-
- {valueChainData.value_chain_structure.nodes_by_level['level_0'].map((node, idx) => (
-
- ))}
-
-
- )}
-
- {(valueChainData.value_chain_structure?.nodes_by_level?.['level_1'] ||
- valueChainData.value_chain_structure?.nodes_by_level?.['level_2']) && (
-
-
- 下游客户
- 客户与终端市场
-
-
- {[
- ...(valueChainData.value_chain_structure?.nodes_by_level?.['level_1'] || []),
- ...(valueChainData.value_chain_structure?.nodes_by_level?.['level_2'] || [])
- ].map((node, idx) => (
-
- ))}
-
-
- )}
-
-
-
-
- {getSankeyChartOption() ? (
-
- ) : (
-
- 暂无流向数据
-
- )}
-
-
-
-
-
- )}
-
- {/* 关键因素与发展时间线 */}
-
-
- {keyFactorsData?.key_factors && (
-
-
-
-
- 关键因素
- {keyFactorsData.key_factors.total_factors} 项
-
-
-
-
-
- {keyFactorsData.key_factors.categories.map((category, idx) => (
-
-
-
-
- {category.category_name}
-
- {category.factors.length}
-
-
-
-
-
-
-
- {category.factors.map((factor, fidx) => (
-
- ))}
-
-
-
- ))}
-
-
-
- )}
-
-
-
- {keyFactorsData?.development_timeline && (
-
-
-
-
- 发展时间线
-
-
- 正面 {keyFactorsData.development_timeline.statistics?.positive_events || 0}
-
-
- 负面 {keyFactorsData.development_timeline.statistics?.negative_events || 0}
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
- {/* 业务板块详情 */}
- {comprehensiveData?.business_segments && comprehensiveData.business_segments.length > 0 && (
-
-
-
-
- 业务板块详情
- {comprehensiveData.business_segments.length} 个板块
-
-
-
-
-
- {comprehensiveData.business_segments.map((segment, idx) => {
- const isExpanded = expandedSegments[idx];
-
- return (
-
-
-
-
- {segment.segment_name}
- }
- onClick={() => toggleSegmentExpansion(idx)}
- colorScheme="blue"
- >
- {isExpanded ? '折叠' : '展开'}
-
-
-
-
- 业务描述
-
- {segment.segment_description || '暂无描述'}
-
-
-
-
- 竞争地位
-
- {segment.competitive_position || '暂无数据'}
-
-
-
-
- 未来潜力
-
- {segment.future_potential || '暂无数据'}
-
-
-
- {isExpanded && segment.key_products && (
-
- 主要产品
-
- {segment.key_products}
-
-
- )}
-
- {isExpanded && segment.market_share && (
-
- 市场份额
-
-
- {segment.market_share}%
-
-
-
- )}
-
- {isExpanded && segment.revenue_contribution && (
-
- 营收贡献
-
-
- {segment.revenue_contribution}%
-
-
-
- )}
-
-
-
- );
- })}
-
-
-
- )}
-
- {/* 战略分析 */}
- {comprehensiveData?.qualitative_analysis?.strategy && (
-
-
-
-
- 战略分析
-
-
-
-
-
-
-
- 战略方向
-
-
- {comprehensiveData.qualitative_analysis.strategy.strategy_description || '暂无数据'}
-
-
-
-
-
-
-
- 战略举措
-
-
- {comprehensiveData.qualitative_analysis.strategy.strategic_initiatives || '暂无数据'}
-
-
-
-
-
-
-
- )}
-
+
{/* 基本信息标签页 */}
-
-
-
-
- 股权结构
- 管理团队
- 公司公告
- 分支机构
- 工商信息
-
-
-
- {/* 股权结构标签页 */}
-
-
- {actualControl.length > 0 && (
-
-
-
- 实际控制人
-
-
-
-
-
-
- {actualControl[0].actual_controller_name}
-
-
- {actualControl[0].control_type}
-
- 截至 {formatUtils.formatDate(actualControl[0].end_date)}
-
-
-
-
- 控制比例
-
- {formatUtils.formatPercentage(actualControl[0].holding_ratio)}
-
-
- {formatUtils.formatShares(actualControl[0].holding_shares)}
-
-
-
-
-
-
- )}
-
- {concentration.length > 0 && (
-
-
-
- 股权集中度
-
-
- {getConcentrationTrend().slice(0, 1).map(([date, items]) => (
-
-
-
- {formatUtils.formatDate(date)}
-
-
-
-
- {Object.entries(items).map(([key, item]) => (
-
- {item.stat_item}
-
-
- {formatUtils.formatPercentage(item.holding_ratio)}
-
- {item.ratio_change && (
- 0 ? 'red' : 'green'}>
- 0 ? FaArrowUp : FaArrowDown} mr={1} boxSize={3} />
- {Math.abs(item.ratio_change).toFixed(2)}%
-
- )}
-
-
- ))}
-
-
-
- ))}
-
-
- )}
-
- {topShareholders.length > 0 && (
-
-
-
- 十大股东
- {formatUtils.formatDate(topShareholders[0].end_date)}
-
-
-
-
-
- | 排名 |
- 股东名称 |
- 股东类型 |
- 持股数量 |
- 持股比例 |
- 股份性质 |
-
-
-
- {topShareholders.slice(0, 10).map((shareholder, idx) => (
-
- |
-
- {shareholder.shareholder_rank}
-
- |
-
-
-
- {shareholder.shareholder_name}
-
-
- |
-
-
- |
-
- {formatUtils.formatShares(shareholder.holding_shares)}
- |
-
-
- {formatUtils.formatPercentage(shareholder.total_share_ratio)}
-
- |
-
-
- {shareholder.share_nature || '流通股'}
-
- |
-
- ))}
-
-
-
-
- )}
-
- {topCirculationShareholders.length > 0 && (
-
-
-
- 十大流通股东
- {formatUtils.formatDate(topCirculationShareholders[0].end_date)}
-
-
-
-
-
- | 排名 |
- 股东名称 |
- 股东类型 |
- 持股数量 |
- 流通股比例 |
-
-
-
- {topCirculationShareholders.slice(0, 10).map((shareholder, idx) => (
-
- |
-
- {shareholder.shareholder_rank}
-
- |
-
-
-
- {shareholder.shareholder_name}
-
-
- |
-
-
- |
-
- {formatUtils.formatShares(shareholder.holding_shares)}
- |
-
-
- {formatUtils.formatPercentage(shareholder.circulation_share_ratio)}
-
- |
-
- ))}
-
-
-
-
- )}
-
-
-
- {/* 管理团队标签页 */}
-
-
- {Object.entries(getManagementByCategory()).map(([category, people]) => (
- people.length > 0 && (
-
-
-
- {category}
- {people.length}人
-
-
-
- {people.map((person, idx) => (
-
-
-
-
-
-
- {person.name}
- {person.gender && (
-
- )}
-
-
- {person.position_name}
-
-
- {person.education && (
-
-
- {person.education}
-
- )}
- {person.birth_year && (
-
- {new Date().getFullYear() - parseInt(person.birth_year)}岁
-
- )}
- {person.nationality && person.nationality !== '中国' && (
-
-
- {person.nationality}
-
- )}
-
-
- 任职日期:{formatUtils.formatDate(person.start_date)}
-
-
-
-
-
- ))}
-
-
- )
- ))}
-
-
-
- {/* 公司公告标签页 */}
-
-
- {disclosureSchedule.length > 0 && (
-
-
-
- 财报披露日程
-
-
- {disclosureSchedule.slice(0, 4).map((schedule, idx) => (
-
-
-
-
- {schedule.report_name}
-
-
- {schedule.is_disclosed ? '已披露' : '预计'}
-
-
- {formatUtils.formatDate(
- schedule.is_disclosed ? schedule.actual_date : schedule.latest_scheduled_date
- )}
-
-
-
-
- ))}
-
-
- )}
-
-
-
-
-
-
- 最新公告
-
-
- {announcements.map((announcement, idx) => (
- {
- setSelectedAnnouncement(announcement);
- onAnnouncementOpen();
- }}
- _hover={{ bg: 'gray.50' }}
- >
-
-
-
-
-
- {announcement.info_type || '公告'}
-
-
- {formatUtils.formatDate(announcement.announce_date)}
-
-
-
- {announcement.title}
-
-
-
- {announcement.format && (
- {announcement.format}
- )}
- }
- variant="ghost"
- onClick={(e) => {
- e.stopPropagation();
- window.open(announcement.url, '_blank');
- }}
- />
-
-
-
-
- ))}
-
-
-
-
-
- {/* 分支机构标签页 */}
-
- {branches.length > 0 ? (
-
- {branches.map((branch, idx) => (
-
-
-
-
- {branch.branch_name}
-
- {branch.business_status}
-
-
-
-
-
- 注册资本
-
- {branch.register_capital || '-'}
-
-
-
- 法人代表
-
- {branch.legal_person || '-'}
-
-
-
- 成立日期
-
- {formatUtils.formatDate(branch.register_date)}
-
-
-
- 关联企业
-
- {branch.related_company_count || 0} 家
-
-
-
-
-
-
- ))}
-
- ) : (
-
-
-
- 暂无分支机构信息
-
-
- )}
-
-
- {/* 工商信息标签页 */}
-
- {basicInfo && (
-
-
-
- 工商信息
-
-
- 统一信用代码
- {basicInfo.credit_code}
-
-
- 公司规模
- {basicInfo.company_size}
-
-
- 注册地址
- {basicInfo.reg_address}
-
-
- 办公地址
- {basicInfo.office_address}
-
-
-
-
-
- 服务机构
-
-
- 会计师事务所
- {basicInfo.accounting_firm}
-
-
- 律师事务所
- {basicInfo.law_firm}
-
-
-
-
-
-
-
-
- 主营业务
- {basicInfo.main_business}
-
-
-
- 经营范围
-
- {basicInfo.business_scope}
-
-
-
- )}
-
-
-
-
-
+
{/* 新闻动态标签页 */}
-
-
-
-
- {/* 搜索框和统计信息 */}
-
-
-
-
-
-
- setNewsSearchQuery(e.target.value)}
- onKeyPress={(e) => e.key === 'Enter' && handleNewsSearch()}
- />
-
-
-
-
- {newsPagination.total > 0 && (
-
-
-
- 共找到 {newsPagination.total} 条新闻
-
-
- )}
-
-
-
-
- {/* 新闻列表 */}
- {newsLoading ? (
-
-
-
- 正在加载新闻...
-
-
- ) : newsEvents.length > 0 ? (
- <>
-
- {newsEvents.map((event, idx) => {
- const importanceColor = {
- 'S': 'red',
- 'A': 'orange',
- 'B': 'yellow',
- 'C': 'green'
- }[event.importance] || 'gray';
-
- const eventTypeIcon = {
- '企业公告': FaBullhorn,
- '政策': FaGavel,
- '技术突破': FaFlask,
- '企业融资': FaDollarSign,
- '政策监管': FaShieldAlt,
- '政策动态': FaFileAlt,
- '行业事件': FaIndustry
- }[event.event_type] || FaNewspaper;
-
- return (
-
-
-
- {/* 标题栏 */}
-
-
-
-
-
- {event.title}
-
-
-
- {/* 标签栏 */}
-
- {event.importance && (
-
- {event.importance}级
-
- )}
- {event.event_type && (
-
- {event.event_type}
-
- )}
- {event.invest_score && (
-
- 投资分: {event.invest_score}
-
- )}
- {event.keywords && event.keywords.length > 0 && (
- <>
- {event.keywords.slice(0, 4).map((keyword, kidx) => (
-
- {typeof keyword === 'string'
- ? keyword
- : (keyword?.concept || keyword?.name || '未知')}
-
- ))}
- >
- )}
-
-
-
- {/* 右侧信息栏 */}
-
-
- {event.created_at ? new Date(event.created_at).toLocaleDateString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit'
- }) : ''}
-
-
- {event.view_count !== undefined && (
-
-
- {event.view_count}
-
- )}
- {event.hot_score !== undefined && (
-
-
- {event.hot_score.toFixed(1)}
-
- )}
-
- {event.creator && (
-
- @{event.creator.username}
-
- )}
-
-
-
- {/* 描述 */}
- {event.description && (
-
- {event.description}
-
- )}
-
- {/* 收益率数据 */}
- {(event.related_avg_chg !== null || event.related_max_chg !== null || event.related_week_chg !== null) && (
-
-
-
-
- 相关涨跌:
-
- {event.related_avg_chg !== null && event.related_avg_chg !== undefined && (
-
- 平均
- 0 ? 'red.500' : 'green.500'}
- >
- {event.related_avg_chg > 0 ? '+' : ''}{event.related_avg_chg.toFixed(2)}%
-
-
- )}
- {event.related_max_chg !== null && event.related_max_chg !== undefined && (
-
- 最大
- 0 ? 'red.500' : 'green.500'}
- >
- {event.related_max_chg > 0 ? '+' : ''}{event.related_max_chg.toFixed(2)}%
-
-
- )}
- {event.related_week_chg !== null && event.related_week_chg !== undefined && (
-
- 周
- 0 ? 'red.500' : 'green.500'}
- >
- {event.related_week_chg > 0 ? '+' : ''}{event.related_week_chg.toFixed(2)}%
-
-
- )}
-
-
- )}
-
-
-
- );
- })}
-
-
- {/* 分页控件 */}
- {newsPagination.pages > 1 && (
-
-
- {/* 分页信息 */}
-
- 第 {newsPagination.page} / {newsPagination.pages} 页
-
-
- {/* 分页按钮 */}
-
-
-
-
- {/* 页码按钮 */}
- {(() => {
- const currentPage = newsPagination.page;
- const totalPages = newsPagination.pages;
- const pageButtons = [];
-
- // 显示当前页及前后各2页
- let startPage = Math.max(1, currentPage - 2);
- let endPage = Math.min(totalPages, currentPage + 2);
-
- // 如果开始页大于1,显示省略号
- if (startPage > 1) {
- pageButtons.push(
- ...
- );
- }
-
- for (let i = startPage; i <= endPage; i++) {
- pageButtons.push(
-
- );
- }
-
- // 如果结束页小于总页数,显示省略号
- if (endPage < totalPages) {
- pageButtons.push(
- ...
- );
- }
-
- return pageButtons;
- })()}
-
-
-
-
-
-
- )}
- >
- ) : (
-
-
-
- 暂无相关新闻
-
- {newsSearchQuery ? '尝试修改搜索关键词' : '该公司暂无新闻动态'}
-
-
-
- )}
-
-
-
-
+
-
- {/* 公告详情模态框 */}
-
-
-
-
-
- {selectedAnnouncement?.title}
-
- {selectedAnnouncement?.info_type}
-
- {formatUtils.formatDate(selectedAnnouncement?.announce_date)}
-
-
-
-
-
-
-
- 文件格式:{selectedAnnouncement?.format}
- 文件大小:{selectedAnnouncement?.file_size} KB
-
-
-
-
-
-
-
-
);
};