From 18c83237e2f842474b589d9e711d3fd3e6ae1f1d Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 9 Dec 2025 17:11:42 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20CompanyOverview=20=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=8C=89=20Tab=20=E6=8B=86=E5=88=86=E4=B8=BA=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E5=AD=90=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 2682 行的大型组件拆分为 4 个模块化文件: - index.js (~550行): 状态管理 + 数据加载 + Tab 容器 - DeepAnalysisTab.js (~1800行): 深度分析 Tab(核心定位、竞争力、产业链) - BasicInfoTab.js (~940行): 基本信息 Tab(股权结构、管理团队、公告) - NewsEventsTab.js (~540行): 新闻动态 Tab(事件列表 + 分页) 重构内容: - 提取 8 个内部子组件到对应 Tab 文件 - 修复 useColorModeValue 在 map 回调中调用的 hooks 规则违规 - 清理未使用的 imports - 完善公告详情模态框(补全 ModalFooter) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../CompanyOverview/BasicInfoTab.js | 941 ++++++ .../CompanyOverview/DeepAnalysisTab.js | 1799 +++++++++++ .../CompanyOverview/NewsEventsTab.js | 541 ++++ .../components/CompanyOverview/index.js | 2650 ++--------------- 4 files changed, 3539 insertions(+), 2392 deletions(-) create mode 100644 src/views/Company/components/CompanyOverview/BasicInfoTab.js create mode 100644 src/views/Company/components/CompanyOverview/DeepAnalysisTab.js create mode 100644 src/views/Company/components/CompanyOverview/NewsEventsTab.js 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}% + + + 50 ? "orange" : "green" + } + borderRadius="full" + /> + + + )} + + + + + + + + 相关公司 + + {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} + + + + + 影响度: + + 70 + ? "red" + : "orange" + } + borderRadius="full" + /> + + {event.impact_metrics?.impact_score || 0} + + + + + + + + ); + })} + + + + {selectedEvent && ( + + + + + + + + {selectedEvent.event_title} + + + {selectedEvent.event_type} + + + {selectedEvent.event_date} + + + + + + + + + + + 事件详情 + + + {selectedEvent.event_desc} + + + + {selectedEvent.related_info?.financial_impact && ( + + + 财务影响 + + + {selectedEvent.related_info.financial_impact} + + + )} + + + + 影响评估 + + + + + 影响度 + + 70 + ? "red" + : "orange" + } + hasStripe + isAnimated + /> + + {selectedEvent.impact_metrics?.impact_score || 0}/100 + + + + + {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} + + + + + + + 业务描述 + + + {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}% - - 50 ? 'orange' : 'green'} - borderRadius="full" - /> - - - )} - - - - - {/* 相关公司列表 */} - - - 相关公司 - {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} - - - - 影响度: - 70 ? 'red' : 'orange'} - borderRadius="full" - /> - - {event.impact_metrics?.impact_score || 0} - - - - - - - - ); - })} - - - - {selectedEvent && ( - - - - - - - - {selectedEvent.event_title} - - - {selectedEvent.event_type} - - {selectedEvent.event_date} - - - - - - - - - 事件详情 - {selectedEvent.event_desc} - - - {selectedEvent.related_info?.financial_impact && ( - - 财务影响 - - {selectedEvent.related_info.financial_impact} - - - )} - - - 影响评估 - - - 影响度 - 70 ? 'red' : 'orange'} - hasStripe - isAnimated - /> - - {selectedEvent.impact_metrics?.impact_score || 0}/100 - - - - - {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} - - - - - 业务描述 - - {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 - - - - - - - - ); };