refactor(CorePositioningCard): 模块化拆分与黑金 UI 优化

- 拆分为独立目录结构: atoms/, theme.ts, index.tsx
- 提取子组件: HighlightCard, ModelBlock, SectionHeader
- 应用黑金风格: 金色边框、透明背景、金色标题

🤖 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:48:56 +08:00
parent b3fb472c66
commit 2c0b06e6a0
7 changed files with 422 additions and 94 deletions

View File

@@ -1,94 +0,0 @@
/**
* 核心定位卡片
*
* 显示公司的核心定位、投资亮点和商业模式
*/
import React from 'react';
import {
Card,
CardBody,
CardHeader,
VStack,
HStack,
Text,
Heading,
Alert,
AlertIcon,
Grid,
GridItem,
Box,
Icon,
} from '@chakra-ui/react';
import { FaLightbulb } from 'react-icons/fa';
import { DisclaimerBox } from '../atoms';
import type { QualitativeAnalysis } from '../types';
interface CorePositioningCardProps {
qualitativeAnalysis: QualitativeAnalysis;
cardBg?: string;
}
const CorePositioningCard: React.FC<CorePositioningCardProps> = ({
qualitativeAnalysis,
cardBg,
}) => {
const blueBg = 'blue.50';
const greenBg = 'green.50';
return (
<Card bg={cardBg} shadow="md">
<CardHeader>
<HStack>
<Icon as={FaLightbulb} color="yellow.500" />
<Heading size="sm"></Heading>
</HStack>
</CardHeader>
<CardBody>
<DisclaimerBox />
<VStack spacing={4} align="stretch">
{qualitativeAnalysis.core_positioning?.one_line_intro && (
<Alert status="info" variant="left-accent">
<AlertIcon />
<Text fontWeight="bold">
{qualitativeAnalysis.core_positioning.one_line_intro}
</Text>
</Alert>
)}
<Grid templateColumns="repeat(2, 1fr)" gap={4}>
<GridItem colSpan={{ base: 2, md: 1 }}>
<VStack align="stretch" spacing={3}>
<Text fontWeight="bold" fontSize="sm" color="gray.600">
</Text>
<Box p={3} bg={blueBg} borderRadius="md">
<Text fontSize="sm" whiteSpace="pre-wrap">
{qualitativeAnalysis.core_positioning?.investment_highlights ||
'暂无数据'}
</Text>
</Box>
</VStack>
</GridItem>
<GridItem colSpan={{ base: 2, md: 1 }}>
<VStack align="stretch" spacing={3}>
<Text fontWeight="bold" fontSize="sm" color="gray.600">
</Text>
<Box p={3} bg={greenBg} borderRadius="md">
<Text fontSize="sm" whiteSpace="pre-wrap">
{qualitativeAnalysis.core_positioning?.business_model_desc ||
'暂无数据'}
</Text>
</Box>
</VStack>
</GridItem>
</Grid>
</VStack>
</CardBody>
</Card>
);
};
export default CorePositioningCard;

View File

@@ -0,0 +1,54 @@
/**
* 投资亮点卡片组件
*/
import React, { memo } from 'react';
import { Box, HStack, VStack, Icon, Text } from '@chakra-ui/react';
import { FaUsers } from 'react-icons/fa';
import { THEME, ICON_MAP, HIGHLIGHT_HOVER_STYLES } from '../theme';
import type { InvestmentHighlightItem } from '../../../types';
interface HighlightCardProps {
highlight: InvestmentHighlightItem;
}
export const HighlightCard = memo<HighlightCardProps>(({ highlight }) => {
const IconComponent = ICON_MAP[highlight.icon] || FaUsers;
return (
<Box
p={4}
bg={THEME.light.cardBg}
borderRadius="lg"
border="1px solid"
borderColor="whiteAlpha.100"
{...HIGHLIGHT_HOVER_STYLES}
transition="border-color 0.2s"
>
<HStack spacing={3} align="flex-start">
<Box
p={2}
bg="whiteAlpha.100"
borderRadius="md"
color={THEME.light.titleColor}
>
<Icon as={IconComponent} boxSize={4} />
</Box>
<VStack align="start" spacing={1} flex={1}>
<Text fontWeight="bold" color={THEME.light.textColor} fontSize="sm">
{highlight.title}
</Text>
<Text
fontSize="xs"
color={THEME.light.subtextColor}
lineHeight="tall"
>
{highlight.description}
</Text>
</VStack>
</HStack>
</Box>
);
});
HighlightCard.displayName = 'HighlightCard';

View File

@@ -0,0 +1,47 @@
/**
* 商业模式板块组件
*/
import React, { memo } from 'react';
import { Box, VStack, HStack, Text, Tag, Divider } from '@chakra-ui/react';
import { THEME } from '../theme';
import type { BusinessModelSection } from '../../../types';
interface ModelBlockProps {
section: BusinessModelSection;
isLast?: boolean;
}
export const ModelBlock = memo<ModelBlockProps>(({ section, isLast }) => (
<Box>
<VStack align="start" spacing={2}>
<Text fontWeight="bold" color={THEME.light.textColor} fontSize="sm">
{section.title}
</Text>
<Text fontSize="xs" color={THEME.light.subtextColor} lineHeight="tall">
{section.description}
</Text>
{section.tags && section.tags.length > 0 && (
<HStack spacing={2} flexWrap="wrap" mt={1}>
{section.tags.map((tag, idx) => (
<Tag
key={idx}
size="sm"
bg={THEME.light.tagBg}
color={THEME.light.tagColor}
borderRadius="full"
px={3}
py={1}
fontSize="xs"
>
{tag}
</Tag>
))}
</HStack>
)}
</VStack>
{!isLast && <Divider my={4} borderColor="whiteAlpha.100" />}
</Box>
));
ModelBlock.displayName = 'ModelBlock';

View File

@@ -0,0 +1,27 @@
/**
* 区域标题组件
*/
import React, { memo } from 'react';
import { HStack, Icon, Text } from '@chakra-ui/react';
import type { IconType } from 'react-icons';
import { THEME } from '../theme';
interface SectionHeaderProps {
icon: IconType;
title: string;
color?: string;
}
export const SectionHeader = memo<SectionHeaderProps>(
({ icon, title, color = THEME.dark.titleColor }) => (
<HStack spacing={2} mb={4}>
<Icon as={icon} color={color} boxSize={4} />
<Text fontWeight="bold" color={color} fontSize="md">
{title}
</Text>
</HStack>
)
);
SectionHeader.displayName = 'SectionHeader';

View File

@@ -0,0 +1,7 @@
/**
* CorePositioningCard 原子组件统一导出
*/
export { SectionHeader } from './SectionHeader';
export { HighlightCard } from './HighlightCard';
export { ModelBlock } from './ModelBlock';

View File

@@ -0,0 +1,204 @@
/**
* 核心定位卡片
*
* 显示公司的核心定位、投资亮点和商业模式
* 黑金主题设计
*/
import React, { memo, useMemo } from 'react';
import {
Card,
CardBody,
VStack,
Text,
Box,
Grid,
GridItem,
} from '@chakra-ui/react';
import { FaCrown, FaStar, FaBriefcase } from 'react-icons/fa';
import type {
QualitativeAnalysis,
InvestmentHighlightItem,
} from '../../types';
import {
THEME,
CARD_STYLES,
GRID_COLUMNS,
BORDER_RIGHT_RESPONSIVE,
} from './theme';
import { SectionHeader, HighlightCard, ModelBlock } from './atoms';
// ==================== 主组件 ====================
interface CorePositioningCardProps {
qualitativeAnalysis: QualitativeAnalysis;
cardBg?: string;
}
const CorePositioningCard: React.FC<CorePositioningCardProps> = memo(
({ qualitativeAnalysis }) => {
const corePositioning = qualitativeAnalysis.core_positioning;
// 判断是否有结构化数据
const hasStructuredData = useMemo(
() =>
!!(
corePositioning?.features?.length ||
(Array.isArray(corePositioning?.investment_highlights) &&
corePositioning.investment_highlights.length > 0) ||
corePositioning?.business_model_sections?.length
),
[corePositioning]
);
// 如果没有结构化数据,使用旧的文本格式渲染
if (!hasStructuredData) {
return (
<Card {...CARD_STYLES}>
<CardBody>
<VStack spacing={4} align="stretch">
<SectionHeader icon={FaCrown} title="核心定位" />
{corePositioning?.one_line_intro && (
<Box
p={4}
bg={THEME.dark.cardBg}
borderRadius="lg"
borderLeft="4px solid"
borderColor={THEME.dark.border}
>
<Text color={THEME.dark.textColor} fontWeight="medium">
{corePositioning.one_line_intro}
</Text>
</Box>
)}
<Grid templateColumns={GRID_COLUMNS.twoColumnMd} gap={4}>
<GridItem>
<Box p={4} bg={THEME.light.cardBg} borderRadius="lg">
<SectionHeader icon={FaStar} title="投资亮点" />
<Text
fontSize="sm"
color={THEME.light.subtextColor}
whiteSpace="pre-wrap"
>
{corePositioning?.investment_highlights_text ||
(typeof corePositioning?.investment_highlights === 'string'
? corePositioning.investment_highlights
: '暂无数据')}
</Text>
</Box>
</GridItem>
<GridItem>
<Box p={4} bg={THEME.light.cardBg} borderRadius="lg">
<SectionHeader icon={FaBriefcase} title="商业模式" />
<Text
fontSize="sm"
color={THEME.light.subtextColor}
whiteSpace="pre-wrap"
>
{corePositioning?.business_model_desc || '暂无数据'}
</Text>
</Box>
</GridItem>
</Grid>
</VStack>
</CardBody>
</Card>
);
}
// 结构化数据渲染 - 缓存数组计算
const highlights = useMemo(
() =>
(Array.isArray(corePositioning?.investment_highlights)
? corePositioning.investment_highlights
: []) as InvestmentHighlightItem[],
[corePositioning?.investment_highlights]
);
const businessSections = useMemo(
() => corePositioning?.business_model_sections || [],
[corePositioning?.business_model_sections]
);
return (
<Card {...CARD_STYLES}>
<CardBody p={0}>
<VStack spacing={0} align="stretch">
{/* 核心定位区域(深色背景) */}
<Box p={6} bg={THEME.dark.bg}>
<SectionHeader icon={FaCrown} title="核心定位" />
{/* 一句话介绍 */}
{corePositioning?.one_line_intro && (
<Box
p={4}
bg={THEME.dark.cardBg}
borderRadius="lg"
borderLeft="4px solid"
borderColor={THEME.dark.border}
>
<Text color={THEME.dark.textColor} fontWeight="medium">
{corePositioning.one_line_intro}
</Text>
</Box>
)}
</Box>
{/* 投资亮点 + 商业模式区域 */}
<Grid templateColumns={GRID_COLUMNS.twoColumn} bg={THEME.light.bg}>
{/* 投资亮点区域 */}
<GridItem
p={6}
borderRight={BORDER_RIGHT_RESPONSIVE}
borderColor="whiteAlpha.100"
>
<SectionHeader icon={FaStar} title="投资亮点" />
<VStack spacing={3} align="stretch">
{highlights.length > 0 ? (
highlights.map((highlight, idx) => (
<HighlightCard key={idx} highlight={highlight} />
))
) : (
<Text fontSize="sm" color={THEME.light.subtextColor}>
</Text>
)}
</VStack>
</GridItem>
{/* 商业模式区域 */}
<GridItem p={6}>
<SectionHeader icon={FaBriefcase} title="商业模式" />
<Box
p={4}
bg={THEME.light.cardBg}
borderRadius="lg"
border="1px solid"
borderColor="whiteAlpha.100"
>
{businessSections.length > 0 ? (
businessSections.map((section, idx) => (
<ModelBlock
key={idx}
section={section}
isLast={idx === businessSections.length - 1}
/>
))
) : (
<Text fontSize="sm" color={THEME.light.subtextColor}>
</Text>
)}
</Box>
</GridItem>
</Grid>
</VStack>
</CardBody>
</Card>
);
}
);
CorePositioningCard.displayName = 'CorePositioningCard';
export default CorePositioningCard;

View File

@@ -0,0 +1,83 @@
/**
* CorePositioningCard 主题和样式常量
*/
import {
FaUniversity,
FaFire,
FaUsers,
FaChartLine,
FaMicrochip,
FaShieldAlt,
} from 'react-icons/fa';
import type { IconType } from 'react-icons';
// ==================== 主题常量 ====================
export const THEME = {
// 深色背景区域(核心定位)
dark: {
bg: '#1A202C',
cardBg: '#252D3A',
border: '#C9A961',
borderGradient: 'linear-gradient(90deg, #C9A961, #8B7355)',
titleColor: '#C9A961',
textColor: '#E2E8F0',
subtextColor: '#A0AEC0',
},
// 浅色背景区域(投资亮点/商业模式)
light: {
bg: '#1E2530',
cardBg: '#252D3A',
titleColor: '#C9A961',
textColor: '#E2E8F0',
subtextColor: '#A0AEC0',
tagBg: 'rgba(201, 169, 97, 0.15)',
tagColor: '#C9A961',
},
} as const;
// ==================== 图标映射 ====================
export const ICON_MAP: Record<string, IconType> = {
bank: FaUniversity,
fire: FaFire,
users: FaUsers,
'trending-up': FaChartLine,
cpu: FaMicrochip,
'shield-check': FaShieldAlt,
};
// ==================== 样式常量 ====================
// 卡片通用样式(含顶部金色边框)
export const CARD_STYLES = {
bg: THEME.dark.bg,
shadow: 'lg',
border: '1px solid',
borderColor: 'whiteAlpha.100',
overflow: 'hidden',
position: 'relative',
_before: {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: THEME.dark.borderGradient,
},
} as const;
// HighlightCard hover 样式
export const HIGHLIGHT_HOVER_STYLES = {
_hover: { borderColor: 'whiteAlpha.200' },
} as const;
// 响应式布局常量
export const GRID_COLUMNS = {
twoColumn: { base: '1fr', lg: 'repeat(2, 1fr)' },
twoColumnMd: { base: '1fr', md: 'repeat(2, 1fr)' },
} as const;
export const BORDER_RIGHT_RESPONSIVE = { lg: '1px solid' } as const;