diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js b/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js deleted file mode 100644 index d686af03..00000000 --- a/src/views/Company/components/CompanyOverview/DeepAnalysisTab.js +++ /dev/null @@ -1,1795 +0,0 @@ -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, - 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 = "gray.50"; - - 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 = `${colorScheme}.50`; - const borderColor = `${colorScheme}.200`; - - 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 = "white"; - const borderColor = "gray.200"; - - 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 = "red.50"; - const negativeBgColor = "green.50"; - - 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 = "blue.50"; - const greenBg = "green.50"; - const purpleBg = "purple.50"; - const orangeBg = "orange.50"; - - 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/DeepAnalysisTab/atoms/BusinessTreeItem.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/BusinessTreeItem.tsx new file mode 100644 index 00000000..1533f27d --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/BusinessTreeItem.tsx @@ -0,0 +1,86 @@ +/** + * 业务结构树形项组件 + * + * 递归显示业务结构层级 + * 使用位置:业务结构分析卡片 + */ + +import React from 'react'; +import { Box, HStack, VStack, Text, Badge, Tag, TagLabel } from '@chakra-ui/react'; +import { formatPercentage, formatBusinessRevenue } from '@utils/priceFormatters'; +import type { BusinessTreeItemProps } from '../types'; + +const BusinessTreeItem: React.FC = ({ business, depth = 0 }) => { + const bgColor = 'gray.50'; + + // 获取营收显示 + const getRevenueDisplay = (): string => { + const revenue = business.revenue || business.financial_metrics?.revenue; + const unit = business.revenue_unit; + if (revenue !== undefined && revenue !== null) { + return formatBusinessRevenue(revenue, unit); + } + return '-'; + }; + + 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 && + business.financial_metrics.revenue_ratio > 30 && ( + + 核心业务 + + )} + + + + 营收占比: {formatPercentage(business.financial_metrics?.revenue_ratio)} + + + 毛利率: {formatPercentage(business.financial_metrics?.gross_margin)} + + {business.growth_metrics?.revenue_growth !== undefined && ( + 0 ? 'red' : 'green' + } + > + + 增长: {business.growth_metrics.revenue_growth > 0 ? '+' : ''} + {formatPercentage(business.growth_metrics.revenue_growth)} + + + )} + + + + + {getRevenueDisplay()} + + + 营业收入 + + + + + ); +}; + +export default BusinessTreeItem; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/DisclaimerBox.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/DisclaimerBox.tsx new file mode 100644 index 00000000..91612f48 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/DisclaimerBox.tsx @@ -0,0 +1,28 @@ +/** + * 免责声明组件 + * + * 显示 AI 分析内容的免责声明警告框 + * 使用位置:深度分析各 Card 底部(共 6 处) + */ + +import React from 'react'; +import { Alert, AlertIcon, Box, Text } from '@chakra-ui/react'; + +const DisclaimerBox: React.FC = () => { + return ( + + + + + 免责声明 + + + 本内容由AI模型基于新闻、公告、研报等公开信息自动分析和生成,未经许可严禁转载。 + 所有内容仅供参考,不构成任何投资建议,请投资者注意风险,独立审慎决策。 + + + + ); +}; + +export default DisclaimerBox; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/KeyFactorCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/KeyFactorCard.tsx new file mode 100644 index 00000000..fb15fc75 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/KeyFactorCard.tsx @@ -0,0 +1,108 @@ +/** + * 关键因素卡片组件 + * + * 显示单个关键因素的详细信息 + * 使用位置:关键因素 Accordion 内 + */ + +import React from 'react'; +import { + Card, + CardBody, + VStack, + HStack, + Text, + Badge, + Tag, + Icon, +} from '@chakra-ui/react'; +import { FaArrowUp, FaArrowDown } from 'react-icons/fa'; +import type { KeyFactorCardProps, ImpactDirection } from '../types'; + +/** + * 获取影响方向对应的颜色 + */ +const getImpactColor = (direction?: ImpactDirection): string => { + const colorMap: Record = { + positive: 'red', + negative: 'green', + neutral: 'gray', + mixed: 'yellow', + }; + return colorMap[direction || 'neutral'] || 'gray'; +}; + +/** + * 获取影响方向的中文标签 + */ +const getImpactLabel = (direction?: ImpactDirection): string => { + const labelMap: Record = { + positive: '正面', + negative: '负面', + neutral: '中性', + mixed: '混合', + }; + return labelMap[direction || 'neutral'] || '中性'; +}; + +const KeyFactorCard: React.FC = ({ factor }) => { + const impactColor = getImpactColor(factor.impact_direction); + const bgColor = 'white'; + const borderColor = 'gray.200'; + + return ( + + + + + + {factor.factor_name} + + + {getImpactLabel(factor.impact_direction)} + + + + + + {factor.factor_value} + {factor.factor_unit && ` ${factor.factor_unit}`} + + {factor.year_on_year !== undefined && ( + 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} + + )} + + + + + ); +}; + +export default KeyFactorCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/ScoreBar.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/ScoreBar.tsx new file mode 100644 index 00000000..338d57f2 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/ScoreBar.tsx @@ -0,0 +1,51 @@ +/** + * 评分进度条组件 + * + * 显示带图标的评分进度条 + * 使用位置:竞争力分析区域(共 8 处) + */ + +import React from 'react'; +import { Box, HStack, Text, Badge, Progress, Icon } from '@chakra-ui/react'; +import type { ScoreBarProps } from '../types'; + +/** + * 根据分数百分比获取颜色方案 + */ +const getColorScheme = (percentage: number): string => { + if (percentage >= 80) return 'purple'; + if (percentage >= 60) return 'blue'; + if (percentage >= 40) return 'yellow'; + return 'orange'; +}; + +const ScoreBar: React.FC = ({ label, score, icon }) => { + const percentage = ((score || 0) / 100) * 100; + const colorScheme = getColorScheme(percentage); + + return ( + + + + {icon && ( + + )} + + {label} + + + {score || 0} + + + + ); +}; + +export default ScoreBar; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/index.ts b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/index.ts new file mode 100644 index 00000000..11267f56 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/atoms/index.ts @@ -0,0 +1,10 @@ +/** + * 原子组件导出 + * + * DeepAnalysisTab 内部使用的基础 UI 组件 + */ + +export { default as DisclaimerBox } from './DisclaimerBox'; +export { default as ScoreBar } from './ScoreBar'; +export { default as BusinessTreeItem } from './BusinessTreeItem'; +export { default as KeyFactorCard } from './KeyFactorCard'; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessSegmentsCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessSegmentsCard.tsx new file mode 100644 index 00000000..c7e63282 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessSegmentsCard.tsx @@ -0,0 +1,157 @@ +/** + * 业务板块详情卡片 + * + * 显示公司各业务板块的详细信息 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Badge, + Box, + Icon, + SimpleGrid, + Button, +} from '@chakra-ui/react'; +import { FaIndustry, FaExpandAlt, FaCompressAlt } from 'react-icons/fa'; +import { DisclaimerBox } from '../atoms'; +import type { BusinessSegment } from '../types'; + +interface BusinessSegmentsCardProps { + businessSegments: BusinessSegment[]; + expandedSegments: Record; + onToggleSegment: (index: number) => void; + cardBg?: string; +} + +const BusinessSegmentsCard: React.FC = ({ + businessSegments, + expandedSegments, + onToggleSegment, + cardBg, +}) => { + if (!businessSegments || businessSegments.length === 0) return null; + + return ( + + + + + 业务板块详情 + {businessSegments.length} 个板块 + + + + + + {businessSegments.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 !== undefined && ( + + + 市场份额 + + + {segment.market_share}% + + + )} + + {isExpanded && segment.revenue_contribution !== undefined && ( + + + 营收贡献 + + + {segment.revenue_contribution}% + + + )} + + + + ); + })} + + + + ); +}; + +export default BusinessSegmentsCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessStructureCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessStructureCard.tsx new file mode 100644 index 00000000..9d51726b --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/BusinessStructureCard.tsx @@ -0,0 +1,58 @@ +/** + * 业务结构分析卡片 + * + * 显示公司业务结构树形图 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Heading, + Badge, + Icon, +} from '@chakra-ui/react'; +import { FaChartPie } from 'react-icons/fa'; +import { DisclaimerBox, BusinessTreeItem } from '../atoms'; +import type { BusinessStructure } from '../types'; + +interface BusinessStructureCardProps { + businessStructure: BusinessStructure[]; + cardBg?: string; +} + +const BusinessStructureCard: React.FC = ({ + businessStructure, + cardBg, +}) => { + if (!businessStructure || businessStructure.length === 0) return null; + + return ( + + + + + 业务结构分析 + {businessStructure[0]?.report_period} + + + + + + {businessStructure.map((business, idx) => ( + + ))} + + + + ); +}; + +export default BusinessStructureCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx new file mode 100644 index 00000000..480c03e4 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx @@ -0,0 +1,182 @@ +/** + * 竞争地位分析卡片 + * + * 显示竞争力评分、雷达图和竞争分析 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Badge, + Tag, + TagLabel, + Grid, + GridItem, + Box, + Icon, + Divider, + SimpleGrid, +} from '@chakra-ui/react'; +import { + FaTrophy, + FaCog, + FaStar, + FaChartLine, + FaDollarSign, + FaFlask, + FaShieldAlt, + FaRocket, + FaUsers, +} from 'react-icons/fa'; +import ReactECharts from 'echarts-for-react'; +import { DisclaimerBox, ScoreBar } from '../atoms'; +import { getRadarChartOption } from '../utils/chartOptions'; +import type { ComprehensiveData } from '../types'; + +interface CompetitiveAnalysisCardProps { + comprehensiveData: ComprehensiveData; + cardBg?: string; +} + +const CompetitiveAnalysisCard: React.FC = ({ + comprehensiveData, + cardBg, +}) => { + const competitivePosition = comprehensiveData.competitive_position; + if (!competitivePosition) return null; + + const radarOption = getRadarChartOption(comprehensiveData); + + return ( + + + + + 竞争地位分析 + {competitivePosition.ranking && ( + + 行业排名 {competitivePosition.ranking.industry_rank}/ + {competitivePosition.ranking.total_companies} + + )} + + + + + + {/* 主要竞争对手 */} + {competitivePosition.analysis?.main_competitors && ( + + + 主要竞争对手 + + + {competitivePosition.analysis.main_competitors + .split(',') + .map((competitor, idx) => ( + + + {competitor.trim()} + + ))} + + + )} + + {/* 评分和雷达图 */} + + + + + + + + + + + + + + + + {radarOption && ( + + )} + + + + + + {/* 竞争优势和劣势 */} + + + + 竞争优势 + + + {competitivePosition.analysis?.competitive_advantages || '暂无数据'} + + + + + 竞争劣势 + + + {competitivePosition.analysis?.competitive_disadvantages || '暂无数据'} + + + + + + ); +}; + +export default CompetitiveAnalysisCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CorePositioningCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CorePositioningCard.tsx new file mode 100644 index 00000000..7138179c --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CorePositioningCard.tsx @@ -0,0 +1,94 @@ +/** + * 核心定位卡片 + * + * 显示公司的核心定位、投资亮点和商业模式 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Alert, + AlertIcon, + Grid, + GridItem, + Box, + Icon, +} from '@chakra-ui/react'; +import { FaLightbulb } from 'react-icons/fa'; +import { DisclaimerBox } from '../atoms'; +import type { QualitativeAnalysis } from '../types'; + +interface CorePositioningCardProps { + qualitativeAnalysis: QualitativeAnalysis; + cardBg?: string; +} + +const CorePositioningCard: React.FC = ({ + qualitativeAnalysis, + cardBg, +}) => { + const blueBg = 'blue.50'; + const greenBg = 'green.50'; + + return ( + + + + + 核心定位 + + + + + + {qualitativeAnalysis.core_positioning?.one_line_intro && ( + + + + {qualitativeAnalysis.core_positioning.one_line_intro} + + + )} + + + + + + 投资亮点 + + + + {qualitativeAnalysis.core_positioning?.investment_highlights || + '暂无数据'} + + + + + + + + + 商业模式 + + + + {qualitativeAnalysis.core_positioning?.business_model_desc || + '暂无数据'} + + + + + + + + + ); +}; + +export default CorePositioningCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/KeyFactorsCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/KeyFactorsCard.tsx new file mode 100644 index 00000000..3c653449 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/KeyFactorsCard.tsx @@ -0,0 +1,78 @@ +/** + * 关键因素卡片 + * + * 显示影响公司的关键因素列表 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Badge, + Box, + Icon, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, +} from '@chakra-ui/react'; +import { FaBalanceScale } from 'react-icons/fa'; +import { DisclaimerBox, KeyFactorCard } from '../atoms'; +import type { KeyFactors } from '../types'; + +interface KeyFactorsCardProps { + keyFactors: KeyFactors; + cardBg?: string; +} + +const KeyFactorsCard: React.FC = ({ + keyFactors, + cardBg, +}) => { + return ( + + + + + 关键因素 + {keyFactors.total_factors} 项 + + + + + + {keyFactors.categories.map((category, idx) => ( + + + + + {category.category_name} + + {category.factors.length} + + + + + + + + {category.factors.map((factor, fidx) => ( + + ))} + + + + ))} + + + + ); +}; + +export default KeyFactorsCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx new file mode 100644 index 00000000..fe411b5f --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/StrategyAnalysisCard.tsx @@ -0,0 +1,79 @@ +/** + * 战略分析卡片 + * + * 显示公司战略方向和战略举措 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Box, + Icon, + Grid, + GridItem, +} from '@chakra-ui/react'; +import { FaRocket } from 'react-icons/fa'; +import { DisclaimerBox } from '../atoms'; +import type { Strategy } from '../types'; + +interface StrategyAnalysisCardProps { + strategy: Strategy; + cardBg?: string; +} + +const StrategyAnalysisCard: React.FC = ({ + strategy, + cardBg, +}) => { + const purpleBg = 'purple.50'; + const orangeBg = 'orange.50'; + + return ( + + + + + 战略分析 + + + + + + + + + 战略方向 + + + + {strategy.strategy_description || '暂无数据'} + + + + + + + + + 战略举措 + + + + {strategy.strategic_initiatives || '暂无数据'} + + + + + + + + ); +}; + +export default StrategyAnalysisCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/TimelineCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/TimelineCard.tsx new file mode 100644 index 00000000..51c05026 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/TimelineCard.tsx @@ -0,0 +1,58 @@ +/** + * 发展时间线卡片 + * + * 显示公司发展历程时间线 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + HStack, + Heading, + Badge, + Box, + Icon, +} from '@chakra-ui/react'; +import { FaHistory } from 'react-icons/fa'; +import { DisclaimerBox } from '../atoms'; +import TimelineComponent from '../organisms/TimelineComponent'; +import type { DevelopmentTimeline } from '../types'; + +interface TimelineCardProps { + developmentTimeline: DevelopmentTimeline; + cardBg?: string; +} + +const TimelineCard: React.FC = ({ + developmentTimeline, + cardBg, +}) => { + return ( + + + + + 发展时间线 + + + 正面 {developmentTimeline.statistics?.positive_events || 0} + + + 负面 {developmentTimeline.statistics?.negative_events || 0} + + + + + + + + + + + + ); +}; + +export default TimelineCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/ValueChainCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/ValueChainCard.tsx new file mode 100644 index 00000000..217aa9bb --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/ValueChainCard.tsx @@ -0,0 +1,185 @@ +/** + * 产业链分析卡片 + * + * 显示产业链层级视图和流向关系 + */ + +import React from 'react'; +import { + Card, + CardBody, + CardHeader, + VStack, + HStack, + Text, + Heading, + Badge, + Box, + Icon, + SimpleGrid, + Tabs, + TabList, + TabPanels, + Tab, + TabPanel, + Center, +} from '@chakra-ui/react'; +import { FaNetworkWired } from 'react-icons/fa'; +import ReactECharts from 'echarts-for-react'; +import { DisclaimerBox } from '../atoms'; +import ValueChainNodeCard from '../organisms/ValueChainNodeCard'; +import { getSankeyChartOption } from '../utils/chartOptions'; +import type { ValueChainData } from '../types'; + +interface ValueChainCardProps { + valueChainData: ValueChainData; + cardBg?: string; +} + +const ValueChainCard: React.FC = ({ + valueChainData, + cardBg, +}) => { + const sankeyOption = getSankeyChartOption(valueChainData); + const nodesByLevel = valueChainData.value_chain_structure?.nodes_by_level; + + // 获取上游节点 + const upstreamNodes = [ + ...(nodesByLevel?.['level_-2'] || []), + ...(nodesByLevel?.['level_-1'] || []), + ]; + + // 获取核心节点 + const coreNodes = nodesByLevel?.['level_0'] || []; + + // 获取下游节点 + const downstreamNodes = [ + ...(nodesByLevel?.['level_1'] || []), + ...(nodesByLevel?.['level_2'] || []), + ]; + + return ( + + + + + 产业链分析 + + + 上游 {valueChainData.analysis_summary?.upstream_nodes || 0} + + + 核心 {valueChainData.analysis_summary?.company_nodes || 0} + + + 下游 {valueChainData.analysis_summary?.downstream_nodes || 0} + + + + + + + + + 层级视图 + 流向关系 + + + + {/* 层级视图 */} + + + {/* 上游供应链 */} + {upstreamNodes.length > 0 && ( + + + + 上游供应链 + + + 原材料与供应商 + + + + {upstreamNodes.map((node, idx) => ( + + ))} + + + )} + + {/* 核心企业 */} + {coreNodes.length > 0 && ( + + + + 核心企业 + + + 公司主体与产品 + + + + {coreNodes.map((node, idx) => ( + + ))} + + + )} + + {/* 下游客户 */} + {downstreamNodes.length > 0 && ( + + + + 下游客户 + + + 客户与终端市场 + + + + {downstreamNodes.map((node, idx) => ( + + ))} + + + )} + + + + {/* 流向关系 */} + + {sankeyOption ? ( + + ) : ( +
+ 暂无流向数据 +
+ )} +
+
+
+
+
+ ); +}; + +export default ValueChainCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/index.ts b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/index.ts new file mode 100644 index 00000000..f3e4d8b7 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/index.ts @@ -0,0 +1,14 @@ +/** + * Card 子组件导出 + * + * DeepAnalysisTab 的各个区块组件 + */ + +export { default as CorePositioningCard } from './CorePositioningCard'; +export { default as CompetitiveAnalysisCard } from './CompetitiveAnalysisCard'; +export { default as BusinessStructureCard } from './BusinessStructureCard'; +export { default as ValueChainCard } from './ValueChainCard'; +export { default as KeyFactorsCard } from './KeyFactorsCard'; +export { default as TimelineCard } from './TimelineCard'; +export { default as BusinessSegmentsCard } from './BusinessSegmentsCard'; +export { default as StrategyAnalysisCard } from './StrategyAnalysisCard'; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx new file mode 100644 index 00000000..5f600040 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx @@ -0,0 +1,117 @@ +/** + * 深度分析 Tab 主组件 + * + * 组合所有子组件,显示公司深度分析内容 + */ + +import React from 'react'; +import { VStack, Center, Text, Spinner, Grid, GridItem } from '@chakra-ui/react'; +import { + CorePositioningCard, + CompetitiveAnalysisCard, + BusinessStructureCard, + ValueChainCard, + KeyFactorsCard, + TimelineCard, + BusinessSegmentsCard, + StrategyAnalysisCard, +} from './components'; +import type { DeepAnalysisTabProps } from './types'; + +const DeepAnalysisTab: React.FC = ({ + comprehensiveData, + valueChainData, + keyFactorsData, + loading, + cardBg, + expandedSegments, + onToggleSegment, +}) => { + // 加载状态 + if (loading) { + return ( +
+ + + 加载深度分析数据... + +
+ ); + } + + return ( + + {/* 核心定位卡片 */} + {comprehensiveData?.qualitative_analysis && ( + + )} + + {/* 竞争地位分析 */} + {comprehensiveData?.competitive_position && ( + + )} + + {/* 业务结构分析 */} + {comprehensiveData?.business_structure && + comprehensiveData.business_structure.length > 0 && ( + + )} + + {/* 产业链分析 */} + {valueChainData && ( + + )} + + {/* 关键因素与发展时间线 */} + + + {keyFactorsData?.key_factors && ( + + )} + + + + {keyFactorsData?.development_timeline && ( + + )} + + + + {/* 业务板块详情 */} + {comprehensiveData?.business_segments && + comprehensiveData.business_segments.length > 0 && ( + + )} + + {/* 战略分析 */} + {comprehensiveData?.qualitative_analysis?.strategy && ( + + )} + + ); +}; + +export default DeepAnalysisTab; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/EventDetailModal.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/EventDetailModal.tsx new file mode 100644 index 00000000..040c47f3 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/EventDetailModal.tsx @@ -0,0 +1,136 @@ +/** + * 事件详情模态框组件 + * + * 显示时间线事件的详细信息 + */ + +import React from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + VStack, + HStack, + Text, + Badge, + Box, + Progress, + Icon, + Button, +} from '@chakra-ui/react'; +import { FaCheckCircle, FaExclamationCircle } from 'react-icons/fa'; +import type { TimelineEvent } from '../../types'; + +interface EventDetailModalProps { + isOpen: boolean; + onClose: () => void; + event: TimelineEvent | null; +} + +const EventDetailModal: React.FC = ({ + isOpen, + onClose, + event, +}) => { + if (!event) return null; + + const isPositive = event.impact_metrics?.is_positive; + const impactScore = event.impact_metrics?.impact_score || 0; + + return ( + + + + + + + + {event.event_title} + + + {event.event_type} + + + {event.event_date} + + + + + + + + + + + 事件详情 + + + {event.event_desc} + + + + {event.related_info?.financial_impact && ( + + + 财务影响 + + + {event.related_info.financial_impact} + + + )} + + + + 影响评估 + + + + + 影响度 + + 70 ? 'red' : 'orange'} + hasStripe + isAnimated + /> + + {impactScore}/100 + + + + + {isPositive ? '正面影响' : '负面影响'} + + + + + + + + + + + + ); +}; + +export default EventDetailModal; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/index.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/index.tsx new file mode 100644 index 00000000..b5a746f2 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/TimelineComponent/index.tsx @@ -0,0 +1,178 @@ +/** + * 时间线组件 + * + * 显示公司发展事件时间线 + */ + +import React, { useState } from 'react'; +import { + Box, + VStack, + HStack, + Text, + Badge, + Card, + CardBody, + Icon, + Progress, + Circle, + Fade, + useDisclosure, +} from '@chakra-ui/react'; +import { + FaCalendarAlt, + FaArrowUp, + FaArrowDown, +} from 'react-icons/fa'; +import EventDetailModal from './EventDetailModal'; +import type { TimelineComponentProps, TimelineEvent } from '../../types'; + +const TimelineComponent: React.FC = ({ events }) => { + const [selectedEvent, setSelectedEvent] = useState(null); + const { isOpen, onOpen, onClose } = useDisclosure(); + + // 背景颜色 + const positiveBgColor = 'red.50'; + const negativeBgColor = 'green.50'; + + const handleEventClick = (event: TimelineEvent) => { + 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} + + + + + + + + ); + })} + + + + + + ); +}; + +export default TimelineComponent; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/RelatedCompaniesModal.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/RelatedCompaniesModal.tsx new file mode 100644 index 00000000..bf900b61 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/RelatedCompaniesModal.tsx @@ -0,0 +1,346 @@ +/** + * 相关公司模态框组件 + * + * 显示产业链节点的相关上市公司列表 + */ + +import React from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + VStack, + HStack, + Text, + Badge, + Card, + CardBody, + Icon, + IconButton, + Center, + Spinner, + Divider, + SimpleGrid, + Box, + Stat, + StatLabel, + StatNumber, + StatHelpText, + Progress, + Tooltip, + Button, +} from '@chakra-ui/react'; +import { ExternalLinkIcon } from '@chakra-ui/icons'; +import { + FaBuilding, + FaHandshake, + FaUserTie, + FaIndustry, + FaCog, + FaNetworkWired, + FaFlask, + FaStar, + FaArrowRight, + FaArrowLeft, +} from 'react-icons/fa'; +import type { ValueChainNode, RelatedCompany } from '../../types'; + +interface RelatedCompaniesModalProps { + isOpen: boolean; + onClose: () => void; + node: ValueChainNode; + isCompany: boolean; + colorScheme: string; + relatedCompanies: RelatedCompany[]; + loadingRelated: boolean; +} + +/** + * 获取节点类型对应的图标 + */ +const getNodeTypeIcon = (type: string) => { + const icons: Record = { + company: FaBuilding, + supplier: FaHandshake, + customer: FaUserTie, + product: FaIndustry, + service: FaCog, + channel: FaNetworkWired, + raw_material: FaFlask, + }; + return icons[type] || FaBuilding; +}; + +/** + * 获取重要度对应的颜色 + */ +const getImportanceColor = (score?: number): string => { + if (!score) return 'green'; + if (score >= 80) return 'red'; + if (score >= 60) return 'orange'; + if (score >= 40) return 'yellow'; + return 'green'; +}; + +/** + * 获取层级标签 + */ +const getLevelLabel = (level?: number): { text: string; color: string } => { + if (level === undefined) return { text: '未知', color: 'gray' }; + if (level < 0) return { text: '上游', color: 'orange' }; + if (level === 0) return { text: '核心', color: 'blue' }; + return { text: '下游', color: 'green' }; +}; + +const RelatedCompaniesModal: React.FC = ({ + isOpen, + onClose, + node, + isCompany, + colorScheme, + relatedCompanies, + loadingRelated, +}) => { + return ( + + + + + + + + {node.node_name} + + {node.node_type} + {isCompany && ( + + 核心企业 + + )} + + + + + + + + {node.node_description && ( + + + 节点描述 + + + {node.node_description} + + + )} + + + + 重要度评分 + + {node.importance_score || 0} + + + + + + + {node.market_share !== undefined && ( + + 市场份额 + {node.market_share}% + + )} + + {node.dependency_degree !== undefined && ( + + 依赖程度 + + {node.dependency_degree}% + + + 50 ? 'orange' : 'green' + } + borderRadius="full" + /> + + + )} + + + + + + + + 相关公司 + + {loadingRelated && } + + {loadingRelated ? ( +
+ +
+ ) : relatedCompanies.length > 0 ? ( + + {relatedCompanies.map((company, idx) => { + 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} + + + + ))} + + + )} + + + + ); + })} + + ) : ( +
+ + + + 暂无相关公司 + + +
+ )} +
+
+
+ + + +
+
+ ); +}; + +export default RelatedCompaniesModal; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/index.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/index.tsx new file mode 100644 index 00000000..5a29c0df --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/organisms/ValueChainNodeCard/index.tsx @@ -0,0 +1,234 @@ +/** + * 产业链节点卡片组件 + * + * 显示产业链中的单个节点,点击可展开查看相关公司 + */ + +import React, { useState } from 'react'; +import { + Card, + CardBody, + VStack, + HStack, + Text, + Badge, + Icon, + Progress, + Box, + Tooltip, + useDisclosure, + useToast, + ScaleFade, +} from '@chakra-ui/react'; +import { + FaBuilding, + FaHandshake, + FaUserTie, + FaIndustry, + FaCog, + FaNetworkWired, + FaFlask, + FaStar, +} from 'react-icons/fa'; +import { logger } from '@utils/logger'; +import { getApiBase } from '@utils/apiConfig'; +import RelatedCompaniesModal from './RelatedCompaniesModal'; +import type { ValueChainNodeCardProps, RelatedCompany } from '../../types'; + +const API_BASE_URL = getApiBase(); + +/** + * 获取节点类型对应的图标 + */ +const getNodeTypeIcon = (type: string) => { + const icons: Record = { + company: FaBuilding, + supplier: FaHandshake, + customer: FaUserTie, + product: FaIndustry, + service: FaCog, + channel: FaNetworkWired, + raw_material: FaFlask, + }; + return icons[type] || FaBuilding; +}; + +/** + * 获取重要度对应的颜色 + */ +const getImportanceColor = (score?: number): string => { + if (!score) return 'green'; + if (score >= 80) return 'red'; + if (score >= 60) return 'orange'; + if (score >= 40) return 'yellow'; + return 'green'; +}; + +const ValueChainNodeCard: React.FC = ({ + node, + isCompany = false, + level = 0, +}) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [relatedCompanies, setRelatedCompanies] = useState([]); + const [loadingRelated, setLoadingRelated] = useState(false); + const toast = useToast(); + + // 根据层级和是否为核心企业确定颜色方案 + const getColorScheme = (): string => { + if (isCompany) return 'blue'; + if (level < 0) return 'orange'; + if (level > 0) return 'green'; + return 'gray'; + }; + + const colorScheme = getColorScheme(); + const bgColor = `${colorScheme}.50`; + const borderColor = `${colorScheme}.200`; + + // 获取相关公司数据 + 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 instanceof Error ? error.message : '未知错误', + status: 'error', + duration: 3000, + isClosable: true, + }); + } finally { + setLoadingRelated(false); + } + }; + + // 点击卡片打开模态框 + const handleCardClick = () => { + onOpen(); + if (relatedCompanies.length === 0) { + fetchRelatedCompanies(); + } + }; + + return ( + <> + + + + + + + + {isCompany && ( + + 核心企业 + + )} + + {node.importance_score !== undefined && + node.importance_score >= 70 && ( + + + + + + )} + + + + {node.node_name} + + + {node.node_description && ( + + {node.node_description} + + )} + + + + {node.node_type} + + {node.market_share !== undefined && ( + + 份额 {node.market_share}% + + )} + + + {node.importance_score !== undefined && ( + + + + 重要度 + + + {node.importance_score} + + + + + )} + + + + + + + + ); +}; + +export default ValueChainNodeCard; diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/types.ts b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/types.ts new file mode 100644 index 00000000..54c988ae --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/types.ts @@ -0,0 +1,326 @@ +/** + * DeepAnalysisTab 组件类型定义 + * + * 深度分析 Tab 所需的所有数据接口类型 + */ + +// ==================== 格式化工具类型 ==================== + +export interface FormatUtils { + formatCurrency: (value: number | null | undefined) => string; + formatBusinessRevenue: (value: number | null | undefined, unit?: string) => string; + formatPercentage: (value: number | null | undefined) => string; +} + +// ==================== 竞争力评分类型 ==================== + +export interface CompetitiveScores { + market_position?: number; + technology?: number; + brand?: number; + operation?: number; + finance?: number; + innovation?: number; + risk?: number; + growth?: number; +} + +export interface CompetitiveRanking { + industry_rank: number; + total_companies: number; +} + +export interface CompetitiveAnalysis { + main_competitors?: string; + competitive_advantages?: string; + competitive_disadvantages?: string; +} + +export interface CompetitivePosition { + scores?: CompetitiveScores; + ranking?: CompetitiveRanking; + analysis?: CompetitiveAnalysis; +} + +// ==================== 核心定位类型 ==================== + +export interface CorePositioning { + one_line_intro?: string; + investment_highlights?: string; + business_model_desc?: string; +} + +export interface Strategy { + strategy_description?: string; + strategic_initiatives?: string; +} + +export interface QualitativeAnalysis { + core_positioning?: CorePositioning; + strategy?: Strategy; +} + +// ==================== 业务结构类型 ==================== + +export interface FinancialMetrics { + revenue?: number; + revenue_ratio?: number; + gross_margin?: number; +} + +export interface GrowthMetrics { + revenue_growth?: number; +} + +export interface BusinessStructure { + business_name: string; + business_level: number; + revenue?: number; + revenue_unit?: string; + financial_metrics?: FinancialMetrics; + growth_metrics?: GrowthMetrics; + report_period?: string; +} + +// ==================== 业务板块类型 ==================== + +export interface BusinessSegment { + segment_name: string; + segment_description?: string; + competitive_position?: string; + future_potential?: string; + key_products?: string; + market_share?: number; + revenue_contribution?: number; +} + +// ==================== 综合数据类型 ==================== + +export interface ComprehensiveData { + qualitative_analysis?: QualitativeAnalysis; + competitive_position?: CompetitivePosition; + business_structure?: BusinessStructure[]; + business_segments?: BusinessSegment[]; +} + +// ==================== 产业链类型 ==================== + +export interface ValueChainNode { + node_name: string; + node_type: string; + node_description?: string; + node_level?: number; + importance_score?: number; + market_share?: number; + dependency_degree?: number; +} + +export interface ValueChainFlow { + source?: { node_name: string }; + target?: { node_name: string }; + flow_metrics?: { + flow_ratio?: string; + }; +} + +export interface NodesByLevel { + [key: string]: ValueChainNode[]; +} + +export interface ValueChainStructure { + nodes_by_level?: NodesByLevel; +} + +export interface AnalysisSummary { + upstream_nodes?: number; + company_nodes?: number; + downstream_nodes?: number; +} + +export interface ValueChainData { + value_chain_flows?: ValueChainFlow[]; + value_chain_structure?: ValueChainStructure; + analysis_summary?: AnalysisSummary; +} + +// ==================== 相关公司类型 ==================== + +export interface RelatedCompanyRelationship { + role: 'source' | 'target'; + connected_node: string; +} + +export interface RelatedCompanyNodeInfo { + node_level?: number; + node_description?: string; +} + +export interface RelatedCompany { + stock_code: string; + stock_name: string; + company_name?: string; + node_info?: RelatedCompanyNodeInfo; + relationships?: RelatedCompanyRelationship[]; +} + +// ==================== 关键因素类型 ==================== + +export type ImpactDirection = 'positive' | 'negative' | 'neutral' | 'mixed'; + +export interface KeyFactor { + factor_name: string; + factor_value: string | number; + factor_unit?: string; + factor_desc?: string; + impact_direction?: ImpactDirection; + impact_weight?: number; + year_on_year?: number; + report_period?: string; +} + +export interface FactorCategory { + category_name: string; + factors: KeyFactor[]; +} + +export interface KeyFactors { + total_factors?: number; + categories: FactorCategory[]; +} + +// ==================== 时间线事件类型 ==================== + +export interface ImpactMetrics { + is_positive?: boolean; + impact_score?: number; +} + +export interface RelatedInfo { + financial_impact?: string; +} + +export interface TimelineEvent { + event_title: string; + event_date: string; + event_type: string; + event_desc: string; + impact_metrics?: ImpactMetrics; + related_info?: RelatedInfo; +} + +export interface TimelineStatistics { + positive_events?: number; + negative_events?: number; +} + +export interface DevelopmentTimeline { + events: TimelineEvent[]; + statistics?: TimelineStatistics; +} + +// ==================== 关键因素数据类型 ==================== + +export interface KeyFactorsData { + key_factors?: KeyFactors; + development_timeline?: DevelopmentTimeline; +} + +// ==================== 主组件 Props 类型 ==================== + +export interface DeepAnalysisTabProps { + comprehensiveData?: ComprehensiveData; + valueChainData?: ValueChainData; + keyFactorsData?: KeyFactorsData; + loading?: boolean; + cardBg?: string; + expandedSegments: Record; + onToggleSegment: (index: number) => void; +} + +// ==================== 子组件 Props 类型 ==================== + +export interface DisclaimerBoxProps { + // 无需 props +} + +export interface ScoreBarProps { + label: string; + score?: number; + icon?: React.ComponentType; +} + +export interface BusinessTreeItemProps { + business: BusinessStructure; + depth?: number; +} + +export interface KeyFactorCardProps { + factor: KeyFactor; +} + +export interface ValueChainNodeCardProps { + node: ValueChainNode; + isCompany?: boolean; + level?: number; +} + +export interface TimelineComponentProps { + events: TimelineEvent[]; +} + +// ==================== 图表配置类型 ==================== + +export interface RadarIndicator { + name: string; + max: number; +} + +export interface RadarChartOption { + tooltip: { trigger: string }; + radar: { + indicator: RadarIndicator[]; + shape: string; + splitNumber: number; + name: { textStyle: { color: string; fontSize: number } }; + splitLine: { lineStyle: { color: string[] } }; + splitArea: { show: boolean; areaStyle: { color: string[] } }; + axisLine: { lineStyle: { color: string } }; + }; + series: Array<{ + name: string; + type: string; + data: Array<{ + value: number[]; + name: string; + symbol: string; + symbolSize: number; + lineStyle: { width: number; color: string }; + areaStyle: { color: string }; + label: { show: boolean; formatter: (params: { value: number }) => number; color: string; fontSize: number }; + }>; + }>; +} + +export interface SankeyNode { + name: string; +} + +export interface SankeyLink { + source: string; + target: string; + value: number; + lineStyle: { color: string; opacity: number }; +} + +export interface SankeyChartOption { + tooltip: { trigger: string; triggerOn: string }; + series: Array<{ + type: string; + layout: string; + emphasis: { focus: string }; + data: SankeyNode[]; + links: SankeyLink[]; + lineStyle: { color: string; curveness: number }; + label: { color: string; fontSize: number }; + }>; +} diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/utils/chartOptions.ts b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/utils/chartOptions.ts new file mode 100644 index 00000000..7164cc30 --- /dev/null +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/utils/chartOptions.ts @@ -0,0 +1,139 @@ +/** + * DeepAnalysisTab 图表配置工具 + * + * 生成雷达图和桑基图的 ECharts 配置 + */ + +import type { + ComprehensiveData, + ValueChainData, + RadarChartOption, + SankeyChartOption, +} from '../types'; + +/** + * 生成竞争力雷达图配置 + * @param comprehensiveData - 综合分析数据 + * @returns ECharts 雷达图配置,或 null(数据不足时) + */ +export const getRadarChartOption = ( + comprehensiveData?: ComprehensiveData +): RadarChartOption | null => { + 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: { value: number }) => params.value, + color: '#3182ce', + fontSize: 10, + }, + }, + ], + }, + ], + }; +}; + +/** + * 生成产业链桑基图配置 + * @param valueChainData - 产业链数据 + * @returns ECharts 桑基图配置,或 null(数据不足时) + */ +export const getSankeyChartOption = ( + valueChainData?: ValueChainData +): SankeyChartOption | null => { + if ( + !valueChainData?.value_chain_flows || + valueChainData.value_chain_flows.length === 0 + ) { + return null; + } + + const nodes = new Set(); + const links: Array<{ + source: string; + target: string; + value: number; + lineStyle: { color: string; opacity: number }; + }> = []; + + 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') || 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 }, + }, + ], + }; +};