feat(DeepAnalysis): 竞争地位分析增加行业排名弹窗

- CompetitiveAnalysisCard 新增 Modal 弹窗展示行业排名详情
  - 点击 Badge 或查看详情按钮可打开弹窗
  - 弹窗采用黑金主题样式
  - StrategyTab 移除独立的 IndustryRankingView 展示
This commit is contained in:
zdl
2025-12-16 16:33:45 +08:00
parent 6a4c475d3a
commit 2eb2a22495
2 changed files with 128 additions and 59 deletions

View File

@@ -2,6 +2,7 @@
* 竞争地位分析卡片 * 竞争地位分析卡片
* *
* 显示竞争力评分、雷达图和竞争分析 * 显示竞争力评分、雷达图和竞争分析
* 包含行业排名弹窗功能
*/ */
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
@@ -22,6 +23,14 @@ import {
Icon, Icon,
Divider, Divider,
SimpleGrid, SimpleGrid,
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
useDisclosure,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { import {
FaTrophy, FaTrophy,
@@ -33,11 +42,32 @@ import {
FaShieldAlt, FaShieldAlt,
FaRocket, FaRocket,
FaUsers, FaUsers,
FaExternalLinkAlt,
} from 'react-icons/fa'; } from 'react-icons/fa';
import ReactECharts from 'echarts-for-react'; import ReactECharts from 'echarts-for-react';
import { ScoreBar } from '../atoms'; import { ScoreBar } from '../atoms';
import { getRadarChartOption } from '../utils/chartOptions'; import { getRadarChartOption } from '../utils/chartOptions';
import type { ComprehensiveData, CompetitivePosition } from '../types'; 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',
},
header: {
color: 'yellow.500',
borderBottomColor: 'rgba(212, 175, 55, 0.2)',
borderBottomWidth: '1px',
},
closeButton: {
color: 'yellow.500',
_hover: { bg: 'rgba(212, 175, 55, 0.1)' },
},
} as const;
// 样式常量 - 避免每次渲染创建新对象 // 样式常量 - 避免每次渲染创建新对象
const CARD_STYLES = { const CARD_STYLES = {
@@ -57,6 +87,7 @@ const CHART_STYLE = { height: '320px' } as const;
interface CompetitiveAnalysisCardProps { interface CompetitiveAnalysisCardProps {
comprehensiveData: ComprehensiveData; comprehensiveData: ComprehensiveData;
industryRankData?: IndustryRankData[];
} }
// 竞争对手标签组件 // 竞争对手标签组件
@@ -141,8 +172,10 @@ const AdvantagesSection = memo<AdvantagesSectionProps>(
AdvantagesSection.displayName = 'AdvantagesSection'; AdvantagesSection.displayName = 'AdvantagesSection';
const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo( const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo(
({ comprehensiveData }) => { ({ comprehensiveData, industryRankData }) => {
const competitivePosition = comprehensiveData.competitive_position; const competitivePosition = comprehensiveData.competitive_position;
const { isOpen, onOpen, onClose } = useDisclosure();
if (!competitivePosition) return null; if (!competitivePosition) return null;
// 缓存雷达图配置 // 缓存雷达图配置
@@ -160,7 +193,11 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo(
[competitivePosition.analysis?.main_competitors] [competitivePosition.analysis?.main_competitors]
); );
// 判断是否有行业排名数据可展示
const hasIndustryRankData = industryRankData && industryRankData.length > 0;
return ( return (
<>
<Card {...CARD_STYLES}> <Card {...CARD_STYLES}>
<CardHeader> <CardHeader>
<HStack> <HStack>
@@ -173,11 +210,26 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo(
border="1px solid" border="1px solid"
borderColor="yellow.600" borderColor="yellow.600"
color="yellow.500" color="yellow.500"
cursor={hasIndustryRankData ? 'pointer' : 'default'}
onClick={hasIndustryRankData ? onOpen : undefined}
_hover={hasIndustryRankData ? { bg: 'rgba(212, 175, 55, 0.1)' } : undefined}
> >
{competitivePosition.ranking.industry_rank}/ {competitivePosition.ranking.industry_rank}/
{competitivePosition.ranking.total_companies} {competitivePosition.ranking.total_companies}
</Badge> </Badge>
)} )}
{hasIndustryRankData && (
<Button
size="xs"
variant="ghost"
color="yellow.500"
rightIcon={<Icon as={FaExternalLinkAlt} boxSize={3} />}
onClick={onOpen}
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
>
</Button>
)}
</HStack> </HStack>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
@@ -210,6 +262,30 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo(
/> />
</CardBody> </CardBody>
</Card> </Card>
{/* 行业排名弹窗 - 黑金主题 */}
<Modal isOpen={isOpen} onClose={onClose} size="4xl" scrollBehavior="inside">
<ModalOverlay bg="blackAlpha.700" />
<ModalContent {...MODAL_STYLES.content}>
<ModalHeader {...MODAL_STYLES.header}>
<HStack>
<Icon as={FaTrophy} color="yellow.500" />
<Text></Text>
</HStack>
</ModalHeader>
<ModalCloseButton {...MODAL_STYLES.closeButton} />
<ModalBody py={4}>
{hasIndustryRankData && (
<IndustryRankingView
industryRank={industryRankData}
bgColor="gray.800"
borderColor="rgba(212, 175, 55, 0.3)"
/>
)}
</ModalBody>
</ModalContent>
</Modal>
</>
); );
} }
); );

View File

@@ -1,7 +1,7 @@
/** /**
* 战略分析 Tab * 战略分析 Tab
* *
* 包含:核心定位 + 战略分析 + 竞争地位分析 + 行业排名 * 包含:核心定位 + 战略分析 + 竞争地位分析(含行业排名弹窗)
*/ */
import React, { memo } from 'react'; import React, { memo } from 'react';
@@ -11,7 +11,6 @@ import {
StrategyAnalysisCard, StrategyAnalysisCard,
CompetitiveAnalysisCard, CompetitiveAnalysisCard,
} from '../components'; } from '../components';
import { IndustryRankingView } from '../../../FinancialPanorama/components';
import type { ComprehensiveData, IndustryRankData } from '../types'; import type { ComprehensiveData, IndustryRankData } from '../types';
export interface StrategyTabProps { export interface StrategyTabProps {
@@ -43,17 +42,11 @@ const StrategyTab: React.FC<StrategyTabProps> = memo(({
/> />
)} )}
{/* 竞争地位分析 */} {/* 竞争地位分析(包含行业排名弹窗) */}
{comprehensiveData?.competitive_position && ( {comprehensiveData?.competitive_position && (
<CompetitiveAnalysisCard comprehensiveData={comprehensiveData} /> <CompetitiveAnalysisCard
)} comprehensiveData={comprehensiveData}
industryRankData={industryRankData}
{/* 行业排名 */}
{industryRankData && industryRankData.length > 0 && (
<IndustryRankingView
industryRank={industryRankData}
bgColor="white"
borderColor="gray.200"
/> />
)} )}
</TabPanelContainer> </TabPanelContainer>