diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx index bff088a8..f63d4b15 100644 --- a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/components/CompetitiveAnalysisCard.tsx @@ -5,7 +5,7 @@ * 包含行业排名弹窗功能 */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo } from "react"; import { Card, CardBody, @@ -28,10 +28,12 @@ import { ModalOverlay, ModalContent, ModalHeader, + UnorderedList, + ListItem, ModalBody, ModalCloseButton, useDisclosure, -} from '@chakra-ui/react'; +} from "@chakra-ui/react"; import { Trophy, Settings, @@ -43,47 +45,51 @@ import { Rocket, Users, ExternalLink, -} from 'lucide-react'; -import ReactECharts from 'echarts-for-react'; -import { ScoreBar } from '../atoms'; -import { getRadarChartOption } from '../utils/chartOptions'; -import { IndustryRankingView } from '../../../FinancialPanorama/components'; -import type { ComprehensiveData, CompetitivePosition, IndustryRankData } from '../types'; +} from "lucide-react"; +import ReactECharts from "echarts-for-react"; +import { ScoreBar } from "../atoms"; +import { getRadarChartOption } from "../utils/chartOptions"; +import { IndustryRankingView } from "../../../FinancialPanorama/components"; +import type { + ComprehensiveData, + CompetitivePosition, + IndustryRankData, +} from "../types"; // 黑金主题弹窗样式 const MODAL_STYLES = { content: { - bg: 'gray.900', - borderColor: 'rgba(212, 175, 55, 0.3)', - borderWidth: '1px', - maxW: '900px', + bg: "gray.900", + borderColor: "rgba(212, 175, 55, 0.3)", + borderWidth: "1px", + maxW: "900px", }, header: { - color: 'yellow.500', - borderBottomColor: 'rgba(212, 175, 55, 0.2)', - borderBottomWidth: '1px', + color: "yellow.500", + borderBottomColor: "rgba(212, 175, 55, 0.2)", + borderBottomWidth: "1px", }, closeButton: { - color: 'yellow.500', - _hover: { bg: 'rgba(212, 175, 55, 0.1)' }, + color: "yellow.500", + _hover: { bg: "rgba(212, 175, 55, 0.1)" }, }, } as const; // 样式常量 - 避免每次渲染创建新对象 const CARD_STYLES = { - bg: 'transparent', - shadow: 'md', + bg: "transparent", + shadow: "md", } as const; const CONTENT_BOX_STYLES = { p: 4, - border: '1px solid', - borderColor: 'yellow.600', - borderRadius: 'md', + border: "1px solid", + borderColor: "yellow.600", + borderRadius: "md", } as const; const GRID_COLSPAN = { base: 2, lg: 1 } as const; -const CHART_STYLE = { height: '320px' } as const; +const CHART_STYLE = { height: "320px" } as const; interface CompetitiveAnalysisCardProps { comprehensiveData: ComprehensiveData; @@ -118,11 +124,11 @@ const CompetitorTags = memo(({ competitors }) => ( )); -CompetitorTags.displayName = 'CompetitorTags'; +CompetitorTags.displayName = "CompetitorTags"; // 评分区域组件 interface ScoreSectionProps { - scores: CompetitivePosition['scores']; + scores: CompetitivePosition["scores"]; } const ScoreSection = memo(({ scores }) => ( @@ -138,7 +144,52 @@ const ScoreSection = memo(({ scores }) => ( )); -ScoreSection.displayName = 'ScoreSection'; +ScoreSection.displayName = "ScoreSection"; + +// 将文本按换行符或分号拆分为列表项 +const parseToList = (text: string): string[] => { + if (!text) return []; + // 优先按换行符拆分,其次按分号拆分 + const items = text.includes("\n") + ? text.split("\n") + : text.split(/[;;、,,]/); + // 清理数字序号(如 "1. ")并过滤空项 + return items.map((s) => s.trim().replace(/^\d+\.\s*/, "")).filter(Boolean); +}; + +// 优劣势列表项组件 +interface AdvantageListProps { + title: string; + content?: string; + color: string; +} + +const AdvantageList = memo(({ title, content, color }) => { + const items = useMemo(() => parseToList(content || ""), [content]); + + return ( + + + {title} + + {items.length > 0 ? ( + + {items.map((item, index) => ( + + {item} + + ))} + + ) : ( + + 暂无数据 + + )} + + ); +}); + +AdvantageList.displayName = "AdvantageList"; // 竞争优劣势组件 interface AdvantagesSectionProps { @@ -149,27 +200,13 @@ interface AdvantagesSectionProps { const AdvantagesSection = memo( ({ advantages, disadvantages }) => ( - - - 竞争优势 - - - {advantages || '暂无数据'} - - - - - 竞争劣势 - - - {disadvantages || '暂无数据'} - - + + ) ); -AdvantagesSection.displayName = 'AdvantagesSection'; +AdvantagesSection.displayName = "AdvantagesSection"; const CompetitiveAnalysisCard: React.FC = memo( ({ comprehensiveData, industryRankData }) => { @@ -179,16 +216,15 @@ const CompetitiveAnalysisCard: React.FC = memo( if (!competitivePosition) return null; // 缓存雷达图配置 - const radarOption = useMemo( - () => getRadarChartOption(comprehensiveData), - [comprehensiveData] - ); + const radarOption = useMemo(() => getRadarChartOption(comprehensiveData), [ + comprehensiveData, + ]); // 缓存竞争对手列表 const competitors = useMemo( () => competitivePosition.analysis?.main_competitors - ?.split(',') + ?.split(",") .map((c) => c.trim()) || [], [competitivePosition.analysis?.main_competitors] ); @@ -202,7 +238,9 @@ const CompetitiveAnalysisCard: React.FC = memo( - 竞争地位分析 + + 竞争地位分析 + {competitivePosition.ranking && ( = memo( border="1px solid" borderColor="yellow.600" color="yellow.500" - cursor={hasIndustryRankData ? 'pointer' : 'default'} + cursor={hasIndustryRankData ? "pointer" : "default"} onClick={hasIndustryRankData ? onOpen : undefined} - _hover={hasIndustryRankData ? { bg: 'rgba(212, 175, 55, 0.1)' } : undefined} + _hover={ + hasIndustryRankData + ? { bg: "rgba(212, 175, 55, 0.1)" } + : undefined + } > 行业排名 {competitivePosition.ranking.industry_rank}/ {competitivePosition.ranking.total_companies} @@ -225,7 +267,7 @@ const CompetitiveAnalysisCard: React.FC = memo( color="yellow.500" rightIcon={} onClick={onOpen} - _hover={{ bg: 'rgba(212, 175, 55, 0.1)' }} + _hover={{ bg: "rgba(212, 175, 55, 0.1)" }} > 查看详情 @@ -234,7 +276,9 @@ const CompetitiveAnalysisCard: React.FC = memo( {/* 主要竞争对手 */} - {competitors.length > 0 && } + {competitors.length > 0 && ( + + )} {/* 评分和雷达图 */} @@ -258,13 +302,20 @@ const CompetitiveAnalysisCard: React.FC = memo( {/* 竞争优势和劣势 */} {/* 行业排名弹窗 - 黑金主题 */} - + @@ -290,6 +341,6 @@ const CompetitiveAnalysisCard: React.FC = memo( } ); -CompetitiveAnalysisCard.displayName = 'CompetitiveAnalysisCard'; +CompetitiveAnalysisCard.displayName = "CompetitiveAnalysisCard"; export default CompetitiveAnalysisCard;