Compare commits

...

6 Commits

Author SHA1 Message Date
zdl
26bc5fece0 style(CompetitiveAnalysisCard): 移除卡片边框
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 10:12:09 +08:00
zdl
1c35ea24cd chore(DeepAnalysisTab): 更新类型定义和组件引用
- types.ts: 扩展类型定义支持新组件结构
- index.tsx: 更新组件 props 传递

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 18:49:17 +08:00
zdl
d76b0d32d6 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>
2025-12-11 18:49:10 +08:00
zdl
eb093a5189 perf(StrategyAnalysisCard): 渲染优化与黑金 UI
- 渲染优化: React.memo, useMemo, 样式常量提取
- 子组件拆分: EmptyState, ContentItem
- 黑金 UI: 金色标题、白色内容文字、空状态金色虚线边框

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 18:49:03 +08:00
zdl
2c0b06e6a0 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>
2025-12-11 18:48:56 +08:00
zdl
b3fb472c66 feat(mock): 更新深度分析 mock 数据
- 核心定位: 更新一句话定位、投资亮点、商业模式
- 战略分析: 添加战略方向和战略举措数据

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 18:48:49 +08:00
12 changed files with 708 additions and 255 deletions

View File

@@ -388,9 +388,62 @@ export const PINGAN_BANK_DATA = {
comprehensiveAnalysis: {
qualitative_analysis: {
core_positioning: {
one_line_intro: '零售基因+综合金融,低估值高弹性股份行',
investment_highlights: '1. 零售AUM 4.2万亿、抵押贷占比63%,低不良+高拨备形成稀缺安全垫\n2. 背靠平安集团,保险-银行-投资生态协同,交叉销售成本趋近于零\n3. 战略收缩高风险消费贷、发力科技/绿色/普惠"五篇大文章",资产重构带来息差与估值双升期权',
business_model_desc: '以零售金融为压舱石,通过按揭、私行财富、信用卡获取低成本负债;对公金融做精行业赛道,输出供应链金融与跨境金融解决方案;同业金融做专投资交易,赚取做市与波段收益。三大条线共享同一中台风控、科技平台与集团客户池,形成"负债降本-资产优价-中收增厚"的正循环,盈利核心=净息差+财富管理手续费+交易价差,集团生态降低获客与资本占用,实现轻资本高回报'
one_line_intro: '中国领先的股份制商业银行,平安集团综合金融战略的核心载体',
// 核心特性(显示在核心定位区域下方的两个卡片)
features: [
{
icon: 'bank',
title: '零售业务',
description: '收入占比超50%个人客户突破1.2亿户零售AUM 4.2万亿'
},
{
icon: 'fire',
title: '综合金融',
description: '交叉销售和客户资源共享带来持续增长,成本趋近于零'
}
],
// 结构化投资亮点
investment_highlights: [
{
icon: 'users',
title: '综合金融优势',
description: '背靠平安集团,客户资源共享和交叉销售带来持续增长动力'
},
{
icon: 'trending-up',
title: '零售转型成效',
description: '零售业务收入占比超50%个人客户突破1.2亿户'
},
{
icon: 'cpu',
title: '金融科技领先',
description: 'AI、大数据、区块链等技术深化应用运营效率持续提升'
},
{
icon: 'shield-check',
title: '风险管理体系',
description: '不良贷款率控制在较低水平,拨备覆盖率保持充足'
}
],
// 结构化商业模式
business_model_sections: [
{
title: '零售银行核心驱动',
description: '以零售银行业务为核心驱动,依托平安集团综合金融平台,构建智能化、移动化、综合化三位一体发展模式。'
},
{
title: '科技赋能转型',
description: '通过科技赋能实现业务流程数字化,降本增效的同时提升客户体验。',
tags: ['AI应用深化', '大数据分析']
},
{
title: '对公业务聚焦',
description: '聚焦供应链金融和产业互联网,服务实体经济高质量发展。'
}
],
// 兼容旧数据格式
investment_highlights_text: '1. 零售AUM 4.2万亿、抵押贷占比63%,低不良+高拨备形成稀缺安全垫\n2. 背靠平安集团,保险-银行-投资生态协同,交叉销售成本趋近于零\n3. 战略收缩高风险消费贷、发力科技/绿色/普惠"五篇大文章",资产重构带来息差与估值双升期权',
business_model_desc: '以零售金融为压舱石,通过按揭、私行财富、信用卡获取低成本负债;对公金融做精行业赛道,输出供应链金融与跨境金融解决方案;同业金融做专投资交易,赚取做市与波段收益。'
},
strategy: {
strategy_description: '以"零售做强、对公做精、同业做专"为主线,通过压降高风险资产、深耕科技绿色普惠、强化集团协同,实现轻资本、弱周期、高股息的高质量增长。',

View File

@@ -4,7 +4,7 @@
* 显示竞争力评分、雷达图和竞争分析
*/
import React from 'react';
import React, { memo, useMemo } from 'react';
import {
Card,
CardBody,
@@ -35,148 +35,185 @@ 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',
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,
}) => {
const competitivePosition = comprehensiveData.competitive_position;
if (!competitivePosition) return null;
// 竞争对手标签组件
interface CompetitorTagsProps {
competitors: string[];
}
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 (
<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 />
CompetitorTags.displayName = 'CompetitorTags';
{/* 主要竞争对手 */}
{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>
)}
// 评分区域组件
interface ScoreSectionProps {
scores: CompetitivePosition['scores'];
}
{/* 评分和雷达图 */}
<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>
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>
));
<GridItem colSpan={{ base: 2, lg: 1 }}>
{radarOption && (
<ReactECharts
option={radarOption}
style={{ height: '320px' }}
theme="light"
/>
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>
)}
</GridItem>
</Grid>
</HStack>
</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>
{/* 竞争优势和劣势 */}
<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>
</CardBody>
</Card>
);
};
<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;

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;

View File

@@ -25,8 +25,6 @@ import type { Strategy } from '../types';
// 样式常量 - 避免每次渲染创建新对象
const CARD_STYLES = {
bg: 'transparent',
border: '1px solid',
borderColor: 'yellow.600',
shadow: 'md',
} as const;
@@ -48,6 +46,7 @@ const GRID_RESPONSIVE_COLSPAN = { base: 2, md: 1 } as const;
interface StrategyAnalysisCardProps {
strategy: Strategy;
cardBg?: string;
}
// 空状态组件 - 独立 memo 避免重复渲染
@@ -74,15 +73,13 @@ interface ContentItemProps {
}
const ContentItem = memo<ContentItemProps>(({ title, content }) => (
<VStack align="stretch" spacing={3}>
<VStack align="stretch" spacing={2}>
<Text fontWeight="bold" fontSize="sm" color="yellow.500">
{title}
</Text>
<Box {...CONTENT_BOX_STYLES}>
<Text fontSize="sm" color="white">
{content}
</Text>
</Box>
<Text fontSize="sm" color="white">
{content}
</Text>
</VStack>
));
@@ -101,27 +98,29 @@ const StrategyAnalysisCard: React.FC<StrategyAnalysisCardProps> = memo(
<CardHeader>
<HStack>
<Icon as={FaRocket} color="yellow.500" />
<Heading size="sm"></Heading>
<Heading size="sm" color="yellow.500"></Heading>
</HStack>
</CardHeader>
<CardBody>
{!hasData ? (
<EmptyState />
) : (
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
<ContentItem
title="战略方向"
content={strategy.strategy_description || '暂无数据'}
/>
</GridItem>
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
<ContentItem
title="战略举措"
content={strategy.strategic_initiatives || '暂无数据'}
/>
</GridItem>
</Grid>
<Box {...CONTENT_BOX_STYLES}>
<Grid templateColumns="repeat(2, 1fr)" gap={6}>
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
<ContentItem
title="战略方向"
content={strategy.strategy_description || '暂无数据'}
/>
</GridItem>
<GridItem colSpan={GRID_RESPONSIVE_COLSPAN}>
<ContentItem
title="战略举措"
content={strategy.strategic_initiatives || '暂无数据'}
/>
</GridItem>
</Grid>
</Box>
)}
</CardBody>
</Card>

View File

@@ -59,10 +59,7 @@ const DeepAnalysisTab: React.FC<DeepAnalysisTabProps> = ({
{/* 竞争地位分析 */}
{comprehensiveData?.competitive_position && (
<CompetitiveAnalysisCard
comprehensiveData={comprehensiveData}
cardBg={cardBg}
/>
<CompetitiveAnalysisCard comprehensiveData={comprehensiveData} />
)}
{/* 业务结构分析 */}

View File

@@ -44,9 +44,48 @@ export interface CompetitivePosition {
// ==================== 核心定位类型 ====================
/** 特性项(用于核心定位下方的两个区块:零售业务/综合金融) */
export interface FeatureItem {
/** 图标名称,如 'bank', 'fire' */
icon: string;
/** 标题,如 '零售业务' */
title: string;
/** 描述文字 */
description: string;
}
/** 投资亮点项(结构化) */
export interface InvestmentHighlightItem {
/** 图标名称,如 'users', 'trending-up' */
icon: string;
/** 标题,如 '综合金融优势' */
title: string;
/** 描述文字 */
description: string;
}
/** 商业模式板块 */
export interface BusinessModelSection {
/** 标题,如 '零售银行核心驱动' */
title: string;
/** 描述文字 */
description: string;
/** 可选的标签,如 ['AI应用深化', '大数据分析'] */
tags?: string[];
}
export interface CorePositioning {
/** 一句话介绍 */
one_line_intro?: string;
investment_highlights?: string;
/** 核心特性2个显示在核心定位区域下方 */
features?: FeatureItem[];
/** 投资亮点 - 支持结构化数组(新格式)或字符串(旧格式) */
investment_highlights?: InvestmentHighlightItem[] | string;
/** 结构化商业模式数组 */
business_model_sections?: BusinessModelSection[];
/** 原 investment_highlights 文本格式(兼容旧数据,优先级低于 investment_highlights */
investment_highlights_text?: string;
/** 商业模式描述(兼容旧数据) */
business_model_desc?: string;
}