220 lines
5.9 KiB
TypeScript
220 lines
5.9 KiB
TypeScript
/**
|
|
* 竞争地位分析卡片
|
|
*
|
|
* 显示竞争力评分、雷达图和竞争分析
|
|
*/
|
|
|
|
import React, { memo, useMemo } 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 { ScoreBar } from '../atoms';
|
|
import { getRadarChartOption } from '../utils/chartOptions';
|
|
import type { ComprehensiveData, CompetitivePosition } from '../types';
|
|
|
|
// 样式常量 - 避免每次渲染创建新对象
|
|
const CARD_STYLES = {
|
|
bg: 'transparent',
|
|
shadow: 'md',
|
|
} as const;
|
|
|
|
const CONTENT_BOX_STYLES = {
|
|
p: 4,
|
|
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;
|
|
|
|
interface CompetitiveAnalysisCardProps {
|
|
comprehensiveData: ComprehensiveData;
|
|
}
|
|
|
|
// 竞争对手标签组件
|
|
interface CompetitorTagsProps {
|
|
competitors: string[];
|
|
}
|
|
|
|
const CompetitorTags = memo<CompetitorTagsProps>(({ competitors }) => (
|
|
<Box mb={4}>
|
|
<Text fontWeight="bold" fontSize="sm" mb={2} color="yellow.500">
|
|
主要竞争对手
|
|
</Text>
|
|
<HStack spacing={2} flexWrap="wrap">
|
|
{competitors.map((competitor, idx) => (
|
|
<Tag
|
|
key={idx}
|
|
size="md"
|
|
variant="outline"
|
|
borderColor="yellow.600"
|
|
color="yellow.500"
|
|
borderRadius="full"
|
|
>
|
|
<Icon as={FaUsers} mr={1} />
|
|
<TagLabel>{competitor}</TagLabel>
|
|
</Tag>
|
|
))}
|
|
</HStack>
|
|
</Box>
|
|
));
|
|
|
|
CompetitorTags.displayName = 'CompetitorTags';
|
|
|
|
// 评分区域组件
|
|
interface ScoreSectionProps {
|
|
scores: CompetitivePosition['scores'];
|
|
}
|
|
|
|
const ScoreSection = memo<ScoreSectionProps>(({ scores }) => (
|
|
<VStack spacing={4} align="stretch">
|
|
<ScoreBar label="市场地位" score={scores?.market_position} icon={FaTrophy} />
|
|
<ScoreBar label="技术实力" score={scores?.technology} icon={FaCog} />
|
|
<ScoreBar label="品牌价值" score={scores?.brand} icon={FaStar} />
|
|
<ScoreBar label="运营效率" score={scores?.operation} icon={FaChartLine} />
|
|
<ScoreBar label="财务健康" score={scores?.finance} icon={FaDollarSign} />
|
|
<ScoreBar label="创新能力" score={scores?.innovation} icon={FaFlask} />
|
|
<ScoreBar label="风险控制" score={scores?.risk} icon={FaShieldAlt} />
|
|
<ScoreBar label="成长潜力" score={scores?.growth} icon={FaRocket} />
|
|
</VStack>
|
|
));
|
|
|
|
ScoreSection.displayName = 'ScoreSection';
|
|
|
|
// 竞争优劣势组件
|
|
interface AdvantagesSectionProps {
|
|
advantages?: string;
|
|
disadvantages?: string;
|
|
}
|
|
|
|
const AdvantagesSection = memo<AdvantagesSectionProps>(
|
|
({ advantages, disadvantages }) => (
|
|
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
|
<Box {...CONTENT_BOX_STYLES}>
|
|
<Text fontWeight="bold" fontSize="sm" mb={2} color="green.400">
|
|
竞争优势
|
|
</Text>
|
|
<Text fontSize="sm" color="white">
|
|
{advantages || '暂无数据'}
|
|
</Text>
|
|
</Box>
|
|
<Box {...CONTENT_BOX_STYLES}>
|
|
<Text fontWeight="bold" fontSize="sm" mb={2} color="red.400">
|
|
竞争劣势
|
|
</Text>
|
|
<Text fontSize="sm" color="white">
|
|
{disadvantages || '暂无数据'}
|
|
</Text>
|
|
</Box>
|
|
</SimpleGrid>
|
|
)
|
|
);
|
|
|
|
AdvantagesSection.displayName = 'AdvantagesSection';
|
|
|
|
const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = memo(
|
|
({ comprehensiveData }) => {
|
|
const competitivePosition = comprehensiveData.competitive_position;
|
|
if (!competitivePosition) return null;
|
|
|
|
// 缓存雷达图配置
|
|
const radarOption = useMemo(
|
|
() => getRadarChartOption(comprehensiveData),
|
|
[comprehensiveData]
|
|
);
|
|
|
|
// 缓存竞争对手列表
|
|
const competitors = useMemo(
|
|
() =>
|
|
competitivePosition.analysis?.main_competitors
|
|
?.split(',')
|
|
.map((c) => c.trim()) || [],
|
|
[competitivePosition.analysis?.main_competitors]
|
|
);
|
|
|
|
return (
|
|
<Card {...CARD_STYLES}>
|
|
<CardHeader>
|
|
<HStack>
|
|
<Icon as={FaTrophy} color="yellow.500" />
|
|
<Heading size="sm" color="yellow.500">竞争地位分析</Heading>
|
|
{competitivePosition.ranking && (
|
|
<Badge
|
|
ml={2}
|
|
bg="transparent"
|
|
border="1px solid"
|
|
borderColor="yellow.600"
|
|
color="yellow.500"
|
|
>
|
|
行业排名 {competitivePosition.ranking.industry_rank}/
|
|
{competitivePosition.ranking.total_companies}
|
|
</Badge>
|
|
)}
|
|
</HStack>
|
|
</CardHeader>
|
|
<CardBody>
|
|
{/* 主要竞争对手 */}
|
|
{/* {competitors.length > 0 && <CompetitorTags competitors={competitors} />} */}
|
|
|
|
{/* 评分和雷达图 */}
|
|
{/* <Grid templateColumns="repeat(2, 1fr)" gap={6}>
|
|
<GridItem colSpan={GRID_COLSPAN}>
|
|
<ScoreSection scores={competitivePosition.scores} />
|
|
</GridItem>
|
|
|
|
<GridItem colSpan={GRID_COLSPAN}>
|
|
{radarOption && (
|
|
<ReactECharts
|
|
option={radarOption}
|
|
style={CHART_STYLE}
|
|
theme="dark"
|
|
/>
|
|
)}
|
|
</GridItem>
|
|
</Grid> */}
|
|
|
|
{/* <Divider my={4} borderColor="yellow.600" /> */}
|
|
|
|
{/* 竞争优势和劣势 */}
|
|
<AdvantagesSection
|
|
advantages={competitivePosition.analysis?.competitive_advantages}
|
|
disadvantages={competitivePosition.analysis?.competitive_disadvantages}
|
|
/>
|
|
</CardBody>
|
|
</Card>
|
|
);
|
|
}
|
|
);
|
|
|
|
CompetitiveAnalysisCard.displayName = 'CompetitiveAnalysisCard';
|
|
|
|
export default CompetitiveAnalysisCard;
|