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:
@@ -4,7 +4,7 @@
|
|||||||
* 显示竞争力评分、雷达图和竞争分析
|
* 显示竞争力评分、雷达图和竞争分析
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
@@ -35,148 +35,187 @@ import {
|
|||||||
FaUsers,
|
FaUsers,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { DisclaimerBox, ScoreBar } from '../atoms';
|
import { ScoreBar } from '../atoms';
|
||||||
import { getRadarChartOption } from '../utils/chartOptions';
|
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 {
|
interface CompetitiveAnalysisCardProps {
|
||||||
comprehensiveData: ComprehensiveData;
|
comprehensiveData: ComprehensiveData;
|
||||||
cardBg?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({
|
// 竞争对手标签组件
|
||||||
comprehensiveData,
|
interface CompetitorTagsProps {
|
||||||
cardBg,
|
competitors: string[];
|
||||||
}) => {
|
}
|
||||||
const competitivePosition = comprehensiveData.competitive_position;
|
|
||||||
if (!competitivePosition) return null;
|
|
||||||
|
|
||||||
const radarOption = getRadarChartOption(comprehensiveData);
|
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>
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
CompetitorTags.displayName = 'CompetitorTags';
|
||||||
<Card bg={cardBg} shadow="md">
|
|
||||||
<CardHeader>
|
|
||||||
<HStack>
|
|
||||||
<Icon as={FaTrophy} color="gold" />
|
|
||||||
<Heading size="sm">竞争地位分析</Heading>
|
|
||||||
{competitivePosition.ranking && (
|
|
||||||
<Badge colorScheme="purple" ml={2}>
|
|
||||||
行业排名 {competitivePosition.ranking.industry_rank}/
|
|
||||||
{competitivePosition.ranking.total_companies}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
</CardHeader>
|
|
||||||
<CardBody>
|
|
||||||
<DisclaimerBox />
|
|
||||||
|
|
||||||
{/* 主要竞争对手 */}
|
// 评分区域组件
|
||||||
{competitivePosition.analysis?.main_competitors && (
|
interface ScoreSectionProps {
|
||||||
<Box mb={4}>
|
scores: CompetitivePosition['scores'];
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 评分和雷达图 */}
|
const ScoreSection = memo<ScoreSectionProps>(({ scores }) => (
|
||||||
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
|
<VStack spacing={4} align="stretch">
|
||||||
<GridItem colSpan={{ base: 2, lg: 1 }}>
|
<ScoreBar label="市场地位" score={scores?.market_position} icon={FaTrophy} />
|
||||||
<VStack spacing={4} align="stretch">
|
<ScoreBar label="技术实力" score={scores?.technology} icon={FaCog} />
|
||||||
<ScoreBar
|
<ScoreBar label="品牌价值" score={scores?.brand} icon={FaStar} />
|
||||||
label="市场地位"
|
<ScoreBar label="运营效率" score={scores?.operation} icon={FaChartLine} />
|
||||||
score={competitivePosition.scores?.market_position}
|
<ScoreBar label="财务健康" score={scores?.finance} icon={FaDollarSign} />
|
||||||
icon={FaTrophy}
|
<ScoreBar label="创新能力" score={scores?.innovation} icon={FaFlask} />
|
||||||
/>
|
<ScoreBar label="风险控制" score={scores?.risk} icon={FaShieldAlt} />
|
||||||
<ScoreBar
|
<ScoreBar label="成长潜力" score={scores?.growth} icon={FaRocket} />
|
||||||
label="技术实力"
|
</VStack>
|
||||||
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>
|
|
||||||
|
|
||||||
<GridItem colSpan={{ base: 2, lg: 1 }}>
|
ScoreSection.displayName = 'ScoreSection';
|
||||||
{radarOption && (
|
|
||||||
<ReactECharts
|
// 竞争优劣势组件
|
||||||
option={radarOption}
|
interface AdvantagesSectionProps {
|
||||||
style={{ height: '320px' }}
|
advantages?: string;
|
||||||
theme="light"
|
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>
|
||||||
)}
|
)}
|
||||||
</GridItem>
|
</HStack>
|
||||||
</Grid>
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
{/* 主要竞争对手 */}
|
||||||
|
{competitors.length > 0 && <CompetitorTags competitors={competitors} />}
|
||||||
|
|
||||||
<Divider my={4} />
|
{/* 评分和雷达图 */}
|
||||||
|
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
|
||||||
|
<GridItem colSpan={GRID_COLSPAN}>
|
||||||
|
<ScoreSection scores={competitivePosition.scores} />
|
||||||
|
</GridItem>
|
||||||
|
|
||||||
{/* 竞争优势和劣势 */}
|
<GridItem colSpan={GRID_COLSPAN}>
|
||||||
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}>
|
{radarOption && (
|
||||||
<Box>
|
<ReactECharts
|
||||||
<Text fontWeight="bold" fontSize="sm" mb={2} color="red.600">
|
option={radarOption}
|
||||||
竞争优势
|
style={CHART_STYLE}
|
||||||
</Text>
|
theme="dark"
|
||||||
<Text fontSize="sm">
|
/>
|
||||||
{competitivePosition.analysis?.competitive_advantages || '暂无数据'}
|
)}
|
||||||
</Text>
|
</GridItem>
|
||||||
</Box>
|
</Grid>
|
||||||
<Box>
|
|
||||||
<Text fontWeight="bold" fontSize="sm" mb={2} color="green.600">
|
<Divider my={4} borderColor="yellow.600" />
|
||||||
竞争劣势
|
|
||||||
</Text>
|
{/* 竞争优势和劣势 */}
|
||||||
<Text fontSize="sm">
|
<AdvantagesSection
|
||||||
{competitivePosition.analysis?.competitive_disadvantages || '暂无数据'}
|
advantages={competitivePosition.analysis?.competitive_advantages}
|
||||||
</Text>
|
disadvantages={competitivePosition.analysis?.competitive_disadvantages}
|
||||||
</Box>
|
/>
|
||||||
</SimpleGrid>
|
</CardBody>
|
||||||
</CardBody>
|
</Card>
|
||||||
</Card>
|
);
|
||||||
);
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
|
CompetitiveAnalysisCard.displayName = 'CompetitiveAnalysisCard';
|
||||||
|
|
||||||
export default CompetitiveAnalysisCard;
|
export default CompetitiveAnalysisCard;
|
||||||
|
|||||||
Reference in New Issue
Block a user