perf(CompetitiveAnalysisCard): 渲染优化与黑金 UI

- 渲染优化: React.memo, useMemo, 样式常量提取
- 子组件拆分: CompetitorTags, ScoreSection, AdvantagesSection
- 黑金 UI: 金色边框、金色标题、白色内容、深色雷达图主题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-11 18:49:10 +08:00
parent eb093a5189
commit d76b0d32d6

View File

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