Compare commits

...

8 Commits

Author SHA1 Message Date
zdl
c1e10e6205 Merge branch 'feature_bugfix/251201_vf_h5_ui' into feature_2025/251209_stock_pref
* feature_bugfix/251201_vf_h5_ui:
  feat: 事件关注功能优化 - Redux 乐观更新 + Mock 数据状态同步
  feat: 投资日历自选股功能优化 - Redux 集成 + 乐观更新
  fix: 修复投资日历切换月份时自动打开事件弹窗的问题
  fix: 修复 CompanyOverview 中 Hooks 顺序错误
2025-12-09 16:36:46 +08:00
zdl
4954c58525 refactor: Company 目录结构重组 - Tab 内容组件文件夹化
- 将 4 个 Tab 内容组件移动到 components/ 目录下
  - CompanyOverview.js → components/CompanyOverview/index.js
  - MarketDataView.js → components/MarketDataView/index.js
  - FinancialPanorama.js → components/FinancialPanorama/index.js
  - ForecastReport.js → components/ForecastReport/index.js
- 更新 CompanyTabs/index.js 导入路径
- 更新 routes/lazy-components.js 路由路径
- 修复组件内相对路径导入,改用 @utils/@services 别名

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 15:31:58 +08:00
zdl
91bd581a5e feat: 添加 useCompanyStock 股票代码管理 2025-12-09 15:18:06 +08:00
zdl
258708fca0 fix: bug修复 2025-12-09 15:16:02 +08:00
zdl
90391729bb feat: 处理自选股乐观更新 2025-12-09 15:15:20 +08:00
zdl
2148d319ad feat: 添加mock 数据 2025-12-09 15:08:15 +08:00
zdl
c61d58b0e3 feat: 添加Company 页面 Tab 切换组件 2025-12-09 15:01:16 +08:00
zdl
ed1c7b9fa9 feat: 添加Company 页面头部组件 CompanyHeader
index.js            # 组合导出
SearchBar.js        # 股票搜索栏
WatchlistButton.js  # 自选股按钮
2025-12-09 14:59:24 +08:00
18 changed files with 1235 additions and 587 deletions

View File

@@ -546,7 +546,9 @@ const InvestmentCalendar = () => {
{concepts && concepts.length > 0 ? (
concepts.slice(0, 3).map((concept, index) => (
<Tag key={index} icon={<TagsOutlined />}>
{Array.isArray(concept) ? concept[0] : concept}
{typeof concept === 'string'
? concept
: (concept?.concept || concept?.name || '未知')}
</Tag>
))
) : (
@@ -940,7 +942,7 @@ const InvestmentCalendar = () => {
<Table
dataSource={selectedStocks}
columns={stockColumns}
rowKey={(record) => record[0]}
rowKey={(record) => record.code}
size="middle"
pagination={false}
/>

View File

@@ -1,37 +1,45 @@
// src/mocks/data/company.js
// 公司相关的 Mock 数据
// 字段名与后端 API 返回格式保持一致
// 平安银行 (000001) 的完整数据
export const PINGAN_BANK_DATA = {
stockCode: '000001',
stockName: '平安银行',
// 基本信息
// 基本信息 - 字段名与后端 API 保持一致
basicInfo: {
code: '000001',
name: '平安银行',
SECCODE: '000001',
SECNAME: '平安银行',
ORGNAME: '平安银行股份有限公司',
english_name: 'Ping An Bank Co., Ltd.',
registered_capital: 1940642.3, // 万元
registered_capital_unit: '万元',
reg_capital: 1940642.3, // 万元
legal_representative: '谢永林',
general_manager: '谢永林',
chairman: '谢永林',
general_manager: '冀光恒',
secretary: '周强',
registered_address: '深圳市深南东路5047号',
office_address: '深圳市深南东路5047号',
reg_address: '深圳市罗湖区深南东路5047号',
office_address: '深圳市福田区益田路5023号平安金融中心',
zipcode: '518001',
phone: '0755-82080387',
tel: '0755-82080387',
fax: '0755-82080386',
email: 'ir@bank.pingan.com',
email: 'ir@pingan.com.cn',
website: 'http://bank.pingan.com',
business_scope: '吸收公众存款;发放短期、中期和长期贷款;办理国内外结算;办理票据承兑与贴现;发行金融债券;代理发行、代理兑付、承销政府债券;买卖政府债券、金融债券;从事同业拆借;买卖、代理买卖外汇;从事银行卡业务;提供信用证服务及担保;代理收付款项及代理保险业务;提供保管箱服务;经有关监管机构批准的其他业务。',
employees: 36542,
introduction: '平安银行股份有限公司是中国平安保险集团股份有限公司控股的一家跨区域经营的股份制商业银行为中国大陆12家全国性股份制商业银行之一。注册资本为人民币51.2335亿元总资产近1.37万亿元,总部位于深圳。平安银行拥有全国性银行经营资质,主要经营商业银行业务。',
list_date: '1991-04-03',
sw_industry_l1: '金融',
sw_industry_l2: '银行',
sw_industry_l3: '股份制银行',
establish_date: '1987-12-22',
list_date: '1991-04-03',
province: '广东省',
city: '深圳市',
industry: '银行',
main_business: '商业银行业务',
credit_code: '914403001000010008',
company_size: '大型企业员工超3万人',
accounting_firm: '普华永道中天会计师事务所(特殊普通合伙)',
law_firm: '北京市金杜律师事务所',
company_intro: '平安银行股份有限公司是中国平安保险集团股份有限公司控股的一家跨区域经营的股份制商业银行为中国大陆12家全国性股份制商业银行之一。总部位于深圳在全国设有超过90家分行、近1000家营业网点。平安银行致力于成为中国最卓越、全球领先的智能化零售银行以科技引领业务发展持续推进零售转型战略。',
main_business: '吸收公众存款、发放贷款、办理结算、票据贴现、资金拆借、银行卡业务、代理收付款项、外汇业务等商业银行业务',
business_scope: '吸收公众存款;发放短期、中期和长期贷款;办理国内外结算;办理票据承兑与贴现;发行金融债券;代理发行、代理兑付、承销政府债券;买卖政府债券、金融债券;从事同业拆借;买卖、代理买卖外汇;从事银行卡业务;提供信用证服务及担保;代理收付款项及代理保险业务;提供保管箱服务;经有关监管机构批准的其他业务。',
employees: 42099,
},
// 实际控制人信息
@@ -60,14 +68,27 @@ export const PINGAN_BANK_DATA = {
management: [
{
name: '谢永林',
position: '董事长、执行董事、行长',
position: '董事长',
gender: '男',
age: 56,
education: '硕士',
appointment_date: '2019-01-01',
annual_compensation: 723.8,
shareholding: 0,
background: '中国平安保险(集团)股份有限公司副总经理兼首席保险业务执行官'
background: '中国平安保险(集团)股份有限公司副总经理兼首席保险业务执行官',
status: 'active'
},
{
name: '冀光恒',
position: '行长',
gender: '男',
age: 52,
education: '博士',
appointment_date: '2023-08-01',
annual_compensation: 650.5,
shareholding: 0,
background: '原中国工商银行总行部门总经理',
status: 'active'
},
{
name: '周强',
@@ -78,7 +99,8 @@ export const PINGAN_BANK_DATA = {
appointment_date: '2016-06-01',
annual_compensation: 542.3,
shareholding: 0.002,
background: '历任平安银行深圳分行行长'
background: '历任平安银行深圳分行行长',
status: 'active'
},
{
name: '郭世邦',
@@ -89,18 +111,8 @@ export const PINGAN_BANK_DATA = {
appointment_date: '2018-03-01',
annual_compensation: 498.6,
shareholding: 0.001,
background: '历任中国平安集团财务负责人'
},
{
name: '蔡新发',
position: '副行长、首席风险官',
gender: '男',
age: 51,
education: '硕士',
appointment_date: '2017-05-01',
annual_compensation: 467.2,
shareholding: 0.0008,
background: '历任平安银行风险管理部总经理'
background: '历任中国平安集团财务负责人',
status: 'active'
},
{
name: '项有志',
@@ -111,7 +123,8 @@ export const PINGAN_BANK_DATA = {
appointment_date: '2019-09-01',
annual_compensation: 425.1,
shareholding: 0,
background: '历任中国平安科技公司总经理'
background: '历任中国平安科技公司总经理',
status: 'active'
}
],
@@ -129,7 +142,7 @@ export const PINGAN_BANK_DATA = {
{ shareholder_name: '挪威中央银行', shares: 87654300, ratio: 0.45, change: 5600000, shareholder_type: '境外法人' }
],
// 十大股东(与流通股东相同,因为平安银行全流通)
// 十大股东
topShareholders: [
{ shareholder_name: '中国平安保险(集团)股份有限公司', shares: 10168542300, ratio: 52.38, change: 0, shareholder_type: '企业', is_restricted: false },
{ shareholder_name: '香港中央结算有限公司', shares: 542138600, ratio: 2.79, change: 12450000, shareholder_type: '境外法人', is_restricted: false },
@@ -205,178 +218,270 @@ export const PINGAN_BANK_DATA = {
{ report_type: '2024年第一季度报告', planned_date: '2024-04-30', status: '已披露' }
],
// 综合分析
// 综合分析 - 结构与组件期望格式匹配
comprehensiveAnalysis: {
overview: {
company_name: '平安银行股份有限公司',
stock_code: '000001',
industry: '银行',
established_date: '1987-12-22',
listing_date: '1991-04-03',
total_assets: 50245.6, // 亿元
net_assets: 3256.8,
registered_capital: 194.06,
employee_count: 36542
qualitative_analysis: {
core_positioning: {
one_line_intro: '中国领先的股份制商业银行,平安集团综合金融战略的核心载体',
investment_highlights: '1. 背靠平安集团,综合金融优势显著,交叉销售和客户资源共享带来持续增长动力;\n2. 零售转型成效显著零售业务收入占比超50%个人客户突破1.2亿户;\n3. 金融科技领先同业AI、大数据、区块链等技术应用深化运营效率持续提升\n4. 风险管理体系完善,不良贷款率控制在较低水平,拨备覆盖率保持充足。',
business_model_desc: '平安银行以零售银行业务为核心驱动,依托平安集团综合金融平台,构建"三位一体"(智能化银行、移动化银行、综合化银行)发展模式。通过科技赋能实现业务流程数字化,降本增效的同时提升客户体验。对公业务聚焦供应链金融和产业互联网,服务实体经济高质量发展。'
},
strategy: '坚持"科技引领、零售突破、对公做精"战略方针,深化数字化转型,打造智能化零售银行标杆。持续推进组织架构扁平化和敏捷化改革,提升经营效率。强化风险管理,保持资产质量稳定。'
},
financial_highlights: {
revenue: 1623.5,
revenue_growth: 8.5,
net_profit: 528.6,
profit_growth: 12.3,
roe: 16.23,
roa: 1.05,
asset_quality_ratio: 1.02,
capital_adequacy_ratio: 13.45,
core_tier1_ratio: 10.82
competitive_position: {
ranking: {
industry_rank: 6,
total_companies: 42
},
analysis: {
main_competitors: '招商银行、兴业银行、中信银行、浦发银行、民生银行',
competitive_advantages: '1. 综合金融优势:依托平安集团综合金融平台,实现银行、保险、投资等业务协同\n2. 科技创新领先金融科技投入占营收比重行业领先AI、大数据应用成熟\n3. 零售客户基础雄厚个人客户1.2亿+财富管理AUM持续增长\n4. 品牌认知度高:平安品牌具有较强的公众认知度和信任度',
competitive_disadvantages: '1. 网点覆盖不如国有大行,在县域地区布局相对薄弱\n2. 对公业务规模与头部股份制银行存在差距\n3. 存款成本相对较高,息差空间受到一定压制'
},
scores: {
market_position: 82,
technology: 90,
brand: 85,
operation: 83,
finance: 86,
innovation: 92,
risk: 84,
growth: 80
}
},
business_structure: [
{ business: '对公业务', revenue: 685.4, ratio: 42.2, growth: 6.8 },
{ business: '零售业务', revenue: 812.3, ratio: 50.1, growth: 11.2 },
{ business: '金融市场业务', revenue: 125.8, ratio: 7.7, growth: 3.5 }
{ business_name: '零售金融', revenue: 81230, ratio: 50.1, growth: 11.2, report_period: '2024Q3' },
{ business_name: '对公金融', revenue: 68540, ratio: 42.2, growth: 6.8, report_period: '2024Q3' },
{ business_name: '资金同业', revenue: 12580, ratio: 7.7, growth: 3.5, report_period: '2024Q3' }
],
competitive_advantages: [
'背靠中国平安集团,综合金融优势明显',
'零售业务转型成效显著,客户基础雄厚',
'金融科技创新能力强,数字化银行建设领先',
'风险管理体系完善,资产质量稳定',
'管理团队经验丰富,执行力强'
],
risk_factors: [
'宏观经济下行压力影响信贷质量',
'利率市场化导致息差收窄',
'金融监管趋严,合规成本上升',
'同业竞争激烈,市场份额面临挑战',
'金融科技发展带来的技术和运营风险'
],
development_strategy: '坚持"科技引领、零售突破、对公做精"战略,加快数字化转型,提升综合金融服务能力',
analyst_rating: {
buy: 18,
hold: 12,
sell: 2,
target_price: 15.8,
current_price: 13.2
}
},
// 价值链分析
valueChainAnalysis: {
upstream: [
{ name: '央行及监管机构', relationship: '政策与监管', importance: '高', description: '接受货币政策调控和监管指导' },
{ name: '同业资金市场', relationship: '资金来源', importance: '高', description: '开展同业拆借、债券回购等业务' },
{ name: '金融科技公司', relationship: '技术支持', importance: '中', description: '提供金融科技解决方案和技术服务' }
],
core_business: {
deposit_business: { scale: 33256.8, market_share: 2.8, growth_rate: 9.2 },
loan_business: { scale: 28945.3, market_share: 2.5, growth_rate: 12.5 },
intermediary_business: { scale: 425.6, market_share: 3.2, growth_rate: 15.8 },
digital_banking: { user_count: 11256, app_mau: 4235, growth_rate: 28.5 }
},
downstream: [
{ name: '个人客户', scale: '1.12亿户', contribution: '50.1%', description: '零售银行业务主体' },
{ name: '企业客户', scale: '85.6万户', contribution: '42.2%', description: '对公业务主体' },
{ name: '政府机构', scale: '2.3万户', contribution: '7.7%', description: '公共事业及政府业务' }
],
ecosystem_partners: [
{ name: '中国平安集团', type: '关联方', cooperation: '综合金融服务、客户共享' },
{ name: '平安科技', type: '科技支持', cooperation: '金融科技研发、系统建设' },
{ name: '平安普惠', type: '业务协同', cooperation: '普惠金融、小微贷款' },
{ name: '平安证券', type: '业务协同', cooperation: '投资银行、资产管理' }
business_segments: [
{
segment_name: '信用卡业务',
description: '国内领先的信用卡发卡银行流通卡量超7000万张',
key_metrics: { cards_issued: 7200, transaction_volume: 28500, market_share: 8.5 }
},
{
segment_name: '财富管理',
description: '私人银行及财富管理业务快速发展AUM突破4万亿',
key_metrics: { aum: 42000, private_banking_customers: 125000, wealth_customers: 1200000 }
},
{
segment_name: '供应链金融',
description: '依托科技平台打造智慧供应链金融生态',
key_metrics: { platform_customers: 35000, financing_balance: 5600, digitization_rate: 95 }
}
]
},
// 关键因素时间线
keyFactorsTimeline: [
{
date: '2024-10-28',
event: '发布2024年三季报',
type: '业绩公告',
importance: 'high',
impact: '前三季度净利润同比增长12.5%,超市场预期',
change: '+5.2%'
// 价值链分析 - 结构与组件期望格式匹配
valueChainAnalysis: {
value_chain_flows: [
{ from: '中国人民银行', to: '平安银行', type: 'regulation', label: '货币政策调控' },
{ from: '银保监会', to: '平安银行', type: 'regulation', label: '监管指导' },
{ from: '同业市场', to: '平安银行', type: 'funding', label: '资金拆借' },
{ from: '债券市场', to: '平安银行', type: 'funding', label: '债券发行' },
{ from: '平安集团', to: '平安银行', type: 'support', label: '综合金融支持' },
{ from: '平安银行', to: '个人客户', type: 'service', label: '零售银行服务' },
{ from: '平安银行', to: '企业客户', type: 'service', label: '对公金融服务' },
{ from: '平安银行', to: '政府机构', type: 'service', label: '政务金融服务' },
{ from: '个人客户', to: '消费场景', type: 'consumption', label: '消费支付' },
{ from: '企业客户', to: '产业链', type: 'production', label: '生产经营' }
],
value_chain_structure: {
nodes_by_level: {
'level_-2': [
{ node_name: '中国人民银行', node_type: 'regulator', description: '制定货币政策,维护金融稳定' },
{ node_name: '银保监会', node_type: 'regulator', description: '银行业监督管理' }
],
'level_-1': [
{ node_name: '同业市场', node_type: 'supplier', description: '银行间资金拆借' },
{ node_name: '债券市场', node_type: 'supplier', description: '债券发行与交易' },
{ node_name: '平安集团', node_type: 'supplier', description: '综合金融平台支撑' },
{ node_name: '金融科技供应商', node_type: 'supplier', description: '技术服务支持' }
],
'level_0': [
{ node_name: '平安银行', node_type: 'company', description: '股份制商业银行', is_core: true }
],
'level_1': [
{ node_name: '个人客户', node_type: 'customer', description: '零售银行服务对象超1.2亿户' },
{ node_name: '企业客户', node_type: 'customer', description: '对公金融服务对象超90万户' },
{ node_name: '政府机构', node_type: 'customer', description: '政务金融服务对象' },
{ node_name: '金融同业', node_type: 'customer', description: '同业金融服务对象' }
],
'level_2': [
{ node_name: '消费场景', node_type: 'end_user', description: '个人消费支付场景' },
{ node_name: '产业链', node_type: 'end_user', description: '企业生产经营场景' },
{ node_name: '公共服务', node_type: 'end_user', description: '政务公共服务场景' }
]
}
},
{
date: '2024-09-15',
event: '推出AI智能客服系统',
type: '科技创新',
importance: 'medium',
impact: '提升客户服务效率,降低运营成本',
change: '+2.1%'
},
{
date: '2024-08-28',
event: '发布2024年中报',
type: '业绩公告',
importance: 'high',
impact: '上半年净利润增长11.2%,资产质量保持稳定',
change: '+3.8%'
},
{
date: '2024-07-20',
event: '获批设立理财子公司',
type: '业务拓展',
importance: 'high',
impact: '完善财富管理业务布局,拓展收入来源',
change: '+4.5%'
},
{
date: '2024-06-10',
event: '完成300亿元二级资本债发行',
type: '融资事件',
importance: 'medium',
impact: '补充资本实力,支持业务扩张',
change: '+1.8%'
},
{
date: '2024-04-30',
event: '发布2024年一季报',
type: '业绩公告',
importance: 'high',
impact: '一季度净利润增长10.8%,开门红表现优异',
change: '+4.2%'
},
{
date: '2024-03-15',
event: '零售客户突破1.1亿户',
type: '业务里程碑',
importance: 'medium',
impact: '零售转型成效显著,客户基础进一步夯实',
change: '+2.5%'
},
{
date: '2024-01-20',
event: '获评"2023年度最佳零售银行"',
type: '荣誉奖项',
importance: 'low',
impact: '品牌影响力提升',
change: '+0.8%'
analysis_summary: {
upstream_nodes: 6,
company_nodes: 1,
downstream_nodes: 7,
total_nodes: 14,
key_insights: '平安银行处于金融产业链核心位置,上游依托央行政策和集团资源,下游服务广泛的个人和企业客户群体'
}
],
},
// 关键因素时间线 - 结构与组件期望格式匹配
keyFactorsTimeline: {
key_factors: {
total_factors: 5,
categories: [
{
category_name: '正面因素',
category_type: 'positive',
factors: [
{
factor_name: '零售转型深化',
impact_score: 9.2,
description: '零售业务收入占比持续提升已超过50%客户基础和AUM稳步增长',
trend: 'improving'
},
{
factor_name: '金融科技领先',
impact_score: 8.8,
description: 'AI、大数据等技术应用深化智能化转型成效显著',
trend: 'stable'
},
{
factor_name: '资产质量稳定',
impact_score: 8.5,
description: '不良贷款率控制在较低水平,风险抵御能力强',
trend: 'stable'
}
]
},
{
category_name: '负面因素',
category_type: 'negative',
factors: [
{
factor_name: '息差压力',
impact_score: 6.5,
description: '利率市场化持续推进,净息差面临收窄压力',
trend: 'declining'
}
]
},
{
category_name: '中性因素',
category_type: 'neutral',
factors: [
{
factor_name: '监管趋严',
impact_score: 7.0,
description: '金融监管持续强化,合规成本有所上升',
trend: 'stable'
}
]
}
]
},
development_timeline: {
statistics: {
positive_events: 6,
negative_events: 0,
neutral_events: 2
},
events: [
{
date: '2024-10-28',
event: '发布2024年三季报',
type: '业绩公告',
importance: 'high',
impact: '前三季度净利润同比增长12.5%,超市场预期',
change: '+5.2%',
sentiment: 'positive'
},
{
date: '2024-09-15',
event: '推出AI智能客服系统3.0',
type: '科技创新',
importance: 'medium',
impact: '客服效率提升40%,客户满意度显著提高',
change: '+2.1%',
sentiment: 'positive'
},
{
date: '2024-08-28',
event: '发布2024年中报',
type: '业绩公告',
importance: 'high',
impact: '上半年净利润增长11.2%,资产质量保持稳定',
change: '+3.8%',
sentiment: 'positive'
},
{
date: '2024-07-20',
event: '平安理财获批新产品资质',
type: '业务拓展',
importance: 'high',
impact: '财富管理业务布局进一步完善',
change: '+4.5%',
sentiment: 'positive'
},
{
date: '2024-06-10',
event: '完成300亿元二级资本债发行',
type: '融资事件',
importance: 'medium',
impact: '补充资本实力,支持业务扩张',
change: '+1.8%',
sentiment: 'neutral'
},
{
date: '2024-04-30',
event: '发布2024年一季报',
type: '业绩公告',
importance: 'high',
impact: '一季度净利润增长10.8%,开门红表现优异',
change: '+4.2%',
sentiment: 'positive'
},
{
date: '2024-03-15',
event: '零售客户突破1.2亿户',
type: '业务里程碑',
importance: 'medium',
impact: '零售转型成效显著,客户基础进一步夯实',
change: '+2.5%',
sentiment: 'positive'
},
{
date: '2024-01-20',
event: '获评"2023年度最佳零售银行"',
type: '荣誉奖项',
importance: 'low',
impact: '品牌影响力提升',
change: '+0.8%',
sentiment: 'neutral'
}
]
}
},
// 盈利预测报告
forecastReport: {
// 营收与利润趋势
income_profit_trend: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
income: [116524, 134632, 148956, 162350, 175280, 189450, 204120], // 营业总收入(百万元)
profit: [34562, 39845, 43218, 52860, 58420, 64680, 71250] // 归母净利润(百万元)
income: [116524, 134632, 148956, 162350, 175280, 189450, 204120],
profit: [34562, 39845, 43218, 52860, 58420, 64680, 71250]
},
// 增长率分析
growth_bars: {
years: ['2021', '2022', '2023', '2024E', '2025E', '2026E'],
revenue_growth_pct: [15.5, 10.6, 8.9, 8.0, 8.1, 7.7] // 营收增长率(%)
revenue_growth_pct: [15.5, 10.6, 8.9, 8.0, 8.1, 7.7]
},
// EPS趋势
eps_trend: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
eps: [1.78, 2.05, 2.23, 2.72, 3.01, 3.33, 3.67] // EPS稀释元/股)
eps: [1.78, 2.05, 2.23, 2.72, 3.01, 3.33, 3.67]
},
// PE与PEG分析
pe_peg_axes: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
pe: [7.4, 6.9, 7.2, 4.9, 4.4, 4.0, 3.6], // PE
peg: [0.48, 0.65, 0.81, 0.55, 0.55, 0.49, 0.47] // PEG
pe: [7.4, 6.9, 7.2, 4.9, 4.4, 4.0, 3.6],
peg: [0.48, 0.65, 0.81, 0.55, 0.55, 0.49, 0.47]
},
// 详细数据表格
detail_table: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
rows: [
@@ -397,137 +502,266 @@ export const PINGAN_BANK_DATA = {
};
// 生成通用公司数据的工具函数
export const generateCompanyData = (stockCode, stockName) => {
export const generateCompanyData = (stockCode, stockName = '示例公司') => {
// 如果是平安银行,直接返回详细数据
if (stockCode === '000001') {
return PINGAN_BANK_DATA;
}
// 否则生成通用数据
// 随机生成一些基础数值
const baseRevenue = Math.floor(Math.random() * 50000) + 10000;
const baseProfit = Math.floor(Math.random() * 5000) + 1000;
const employeeCount = Math.floor(Math.random() * 20000) + 1000;
// 生成通用数据,结构与组件期望格式匹配
return {
stockCode,
stockName,
basicInfo: {
code: stockCode,
name: stockName,
registered_capital: Math.floor(Math.random() * 500000) + 10000,
registered_capital_unit: '万元',
SECCODE: stockCode,
SECNAME: stockName,
ORGNAME: `${stockName}股份有限公司`,
english_name: `${stockName} Co., Ltd.`,
reg_capital: Math.floor(Math.random() * 500000) + 10000,
legal_representative: '张三',
chairman: '张三',
general_manager: '李四',
secretary: '王五',
registered_address: '中国某省某市某区某路123号',
office_address: '中国某省某市某区某路123号',
phone: '021-12345678',
reg_address: '中国某省某市某区某路123号',
office_address: '中国某省某市某区某路456号',
zipcode: '100000',
tel: '010-12345678',
fax: '010-12345679',
email: 'ir@company.com',
website: 'http://www.company.com',
employees: Math.floor(Math.random() * 10000) + 1000,
list_date: '2010-01-01',
industry: '制造业',
sw_industry_l1: '制造业',
sw_industry_l2: '电子设备',
sw_industry_l3: '消费电子',
establish_date: '2005-01-01',
list_date: '2010-06-15',
province: '广东省',
city: '深圳市',
credit_code: '91440300XXXXXXXXXX',
company_size: '中型企业',
accounting_firm: '安永华明会计师事务所',
law_firm: '北京市君合律师事务所',
company_intro: `${stockName}股份有限公司是一家专注于XX领域的高科技企业致力于为客户提供优质的产品和服务。公司拥有完善的研发体系和生产能力在行业内具有较强的竞争力。`,
main_business: '电子产品的研发、生产和销售',
business_scope: '电子产品、通信设备、计算机软硬件的研发、生产、销售;技术咨询、技术服务;货物进出口、技术进出口。',
employees: employeeCount,
},
actualControl: {
controller_name: '某控股集团有限公司',
controller_type: '企业',
shareholding_ratio: 35.5,
control_chain: '某控股集团有限公司 -> ' + stockName,
control_chain: `某控股集团有限公司 -> ${stockName}股份有限公司`,
is_listed: false,
change_date: '2023-12-31',
},
concentration: {
top1_ratio: 35.5,
top3_ratio: 52.3,
top5_ratio: 61.8,
top10_ratio: 72.5,
update_date: '2024-09-30',
concentration_level: '适度集中',
herfindahl_index: 0.1856,
},
management: [
{ name: '张三', position: '董事长', gender: '男', age: 55, education: '硕士', annual_compensation: 320.5 },
{ name: '李四', position: '总经理', gender: '男', age: 50, education: '硕士', annual_compensation: 280.3 },
{ name: '王五', position: '董事会秘书', gender: '女', age: 45, education: '本科', annual_compensation: 180.2 },
{ name: '张三', position: '董事长', gender: '男', age: 55, education: '硕士', annual_compensation: 320.5, status: 'active' },
{ name: '李四', position: '总经理', gender: '男', age: 50, education: '硕士', annual_compensation: 280.3, status: 'active' },
{ name: '王五', position: '董事会秘书', gender: '女', age: 45, education: '本科', annual_compensation: 180.2, status: 'active' },
{ name: '赵六', position: '财务总监', gender: '男', age: 48, education: '硕士', annual_compensation: 200.5, status: 'active' },
{ name: '钱七', position: '技术总监', gender: '男', age: 42, education: '博士', annual_compensation: 250.8, status: 'active' },
],
topCirculationShareholders: Array(10).fill(null).map((_, i) => ({
shareholder_name: `股东${i + 1}`,
shares: Math.floor(Math.random() * 100000000),
ratio: (10 - i) * 0.8,
ratio: parseFloat(((10 - i) * 0.8 + Math.random() * 2).toFixed(2)),
change: Math.floor(Math.random() * 10000000) - 5000000,
shareholder_type: '企业'
shareholder_type: i < 3 ? '企业' : (i < 6 ? '个人' : '机构')
})),
topShareholders: Array(10).fill(null).map((_, i) => ({
shareholder_name: `股东${i + 1}`,
shares: Math.floor(Math.random() * 100000000),
ratio: (10 - i) * 0.8,
ratio: parseFloat(((10 - i) * 0.8 + Math.random() * 2).toFixed(2)),
change: Math.floor(Math.random() * 10000000) - 5000000,
shareholder_type: '企业',
is_restricted: false
shareholder_type: i < 3 ? '企业' : (i < 6 ? '个人' : '机构'),
is_restricted: i < 2
})),
branches: [
{ name: '北京分公司', address: '北京市朝阳区某路123号', phone: '010-12345678', type: '分公司' },
{ name: '上海分公司', address: '上海市浦东新区某路456号', phone: '021-12345678', type: '分公司' },
{ name: '北京分公司', address: '北京市朝阳区某路123号', phone: '010-12345678', type: '分公司', establish_date: '2012-05-01' },
{ name: '上海分公司', address: '上海市浦东新区某路456号', phone: '021-12345678', type: '分公司', establish_date: '2013-08-15' },
{ name: '广州分公司', address: '广州市天河区某路789号', phone: '020-12345678', type: '分公司', establish_date: '2014-03-20' },
],
announcements: [
{ title: stockName + '2024年第三季度报告', publish_date: '2024-10-28', type: '定期报告', summary: '业绩稳步增长' },
{ title: stockName + '2024年半年度报告', publish_date: '2024-08-28', type: '定期报告', summary: '经营情况良好' },
{ title: `${stockName}2024年第三季度报告`, publish_date: '2024-10-28', type: '定期报告', summary: '业绩稳步增长', url: '#' },
{ title: `${stockName}2024年半年度报告`, publish_date: '2024-08-28', type: '定期报告', summary: '经营情况良好', url: '#' },
{ title: `关于重大合同签订的公告`, publish_date: '2024-07-15', type: '临时公告', summary: '签订重要销售合同', url: '#' },
],
disclosureSchedule: [
{ report_type: '2024年年度报告', planned_date: '2025-04-30', status: '未披露' },
{ report_type: '2024年第三季度报告', planned_date: '2024-10-31', status: '已披露' },
{ report_type: '2024年半年度报告', planned_date: '2024-08-31', status: '已披露' },
],
comprehensiveAnalysis: {
overview: {
company_name: stockName,
stock_code: stockCode,
industry: '制造业',
total_assets: Math.floor(Math.random() * 10000) + 100,
qualitative_analysis: {
core_positioning: {
one_line_intro: `${stockName}是XX行业的领先企业专注于为客户提供创新解决方案`,
investment_highlights: '1. 行业龙头地位,市场份额领先\n2. 技术研发实力强,专利储备丰富\n3. 客户资源优质,大客户粘性高\n4. 管理团队经验丰富,执行力强',
business_model_desc: `${stockName}采用"研发+生产+销售"一体化经营模式,通过持续的技术创新和产品迭代,为客户提供高性价比的产品和服务。`
},
strategy: '坚持技术创新驱动发展,深耕核心业务领域,积极拓展新兴市场,持续提升企业核心竞争力。'
},
financial_highlights: {
revenue: Math.floor(Math.random() * 1000) + 50,
revenue_growth: (Math.random() * 20 - 5).toFixed(2),
net_profit: Math.floor(Math.random() * 100) + 10,
profit_growth: (Math.random() * 20 - 5).toFixed(2),
competitive_position: {
ranking: {
industry_rank: Math.floor(Math.random() * 20) + 1,
total_companies: 150
},
analysis: {
main_competitors: '竞争对手A、竞争对手B、竞争对手C',
competitive_advantages: '技术领先、品牌优势、客户资源丰富、管理团队优秀',
competitive_disadvantages: '规模相对较小、区域布局有待完善'
},
scores: {
market_position: Math.floor(Math.random() * 20) + 70,
technology: Math.floor(Math.random() * 20) + 70,
brand: Math.floor(Math.random() * 20) + 65,
operation: Math.floor(Math.random() * 20) + 70,
finance: Math.floor(Math.random() * 20) + 70,
innovation: Math.floor(Math.random() * 20) + 70,
risk: Math.floor(Math.random() * 20) + 70,
growth: Math.floor(Math.random() * 20) + 70
}
},
competitive_advantages: ['技术领先', '品牌优势', '管理团队优秀'],
risk_factors: ['市场竞争激烈', '原材料价格波动'],
business_structure: [
{ business_name: '核心产品', revenue: baseRevenue * 0.6, ratio: 60, growth: 12.5, report_period: '2024Q3' },
{ business_name: '增值服务', revenue: baseRevenue * 0.25, ratio: 25, growth: 18.2, report_period: '2024Q3' },
{ business_name: '其他业务', revenue: baseRevenue * 0.15, ratio: 15, growth: 5.8, report_period: '2024Q3' }
],
business_segments: []
},
valueChainAnalysis: {
upstream: [
{ name: '原材料供应商A', relationship: '供应商', importance: '' },
{ name: '原材料供应商B', relationship: '供应商', importance: '' },
],
downstream: [
{ name: '经销商网络', scale: '1000家', contribution: '60%' },
{ name: '直营渠道', scale: '100家', contribution: '40%' },
value_chain_flows: [
{ from: '原材料供应商', to: stockName, type: 'supply', label: '原材料采购' },
{ from: '设备供应商', to: stockName, type: 'supply', label: '设备采购' },
{ from: stockName, to: '直销客户', type: 'sales', label: '直销' },
{ from: stockName, to: '经销商', type: 'sales', label: '分销' },
{ from: '经销商', to: '终端用户', type: 'distribution', label: '零售' }
],
value_chain_structure: {
nodes_by_level: {
'level_-2': [
{ node_name: '原材料供应商', node_type: 'supplier', description: '提供生产所需原材料' }
],
'level_-1': [
{ node_name: '设备供应商', node_type: 'supplier', description: '提供生产设备' },
{ node_name: '技术服务商', node_type: 'supplier', description: '提供技术支持' }
],
'level_0': [
{ node_name: stockName, node_type: 'company', description: '核心企业', is_core: true }
],
'level_1': [
{ node_name: '直销客户', node_type: 'customer', description: '大客户直销' },
{ node_name: '经销商', node_type: 'customer', description: '渠道分销' }
],
'level_2': [
{ node_name: '终端用户', node_type: 'end_user', description: '最终消费者' }
]
}
},
analysis_summary: {
upstream_nodes: 3,
company_nodes: 1,
downstream_nodes: 3,
total_nodes: 7,
key_insights: `${stockName}在产业链中处于核心位置,上下游关系稳定`
}
},
keyFactorsTimeline: {
key_factors: {
total_factors: 3,
categories: [
{
category_name: '正面因素',
category_type: 'positive',
factors: [
{
factor_name: '业绩增长',
impact_score: 8.5,
description: '营收和利润保持稳定增长态势',
trend: 'improving'
}
]
},
{
category_name: '负面因素',
category_type: 'negative',
factors: [
{
factor_name: '原材料成本',
impact_score: 6.0,
description: '原材料价格波动影响毛利率',
trend: 'declining'
}
]
},
{
category_name: '中性因素',
category_type: 'neutral',
factors: [
{
factor_name: '市场竞争',
impact_score: 7.0,
description: '行业竞争加剧,需持续提升竞争力',
trend: 'stable'
}
]
}
]
},
development_timeline: {
statistics: {
positive_events: 4,
negative_events: 0,
neutral_events: 0
},
events: [
{ date: '2024-10-28', event: '发布三季报', type: '业绩公告', importance: 'high', impact: '业绩超预期', change: '+3.5%', sentiment: 'positive' },
{ date: '2024-08-28', event: '发布中报', type: '业绩公告', importance: 'high', impact: '业绩稳定增长', change: '+2.8%', sentiment: 'positive' },
{ date: '2024-06-15', event: '新产品发布', type: '产品发布', importance: 'medium', impact: '丰富产品线', change: '+1.5%', sentiment: 'positive' },
{ date: '2024-04-28', event: '发布一季报', type: '业绩公告', importance: 'high', impact: '开门红', change: '+2.2%', sentiment: 'positive' }
]
}
},
keyFactorsTimeline: [
{ date: '2024-10-28', event: '发布三季报', type: '业绩公告', importance: 'high', impact: '业绩超预期' },
{ date: '2024-08-28', event: '发布中报', type: '业绩公告', importance: 'high', impact: '业绩稳定增长' },
],
// 通用预测报告数据
forecastReport: {
income_profit_trend: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
income: [5000, 5800, 6500, 7200, 7900, 8600, 9400],
profit: [450, 520, 580, 650, 720, 800, 890]
income: [baseRevenue * 0.6, baseRevenue * 0.7, baseRevenue * 0.8, baseRevenue * 0.9, baseRevenue, baseRevenue * 1.1, baseRevenue * 1.2],
profit: [baseProfit * 0.6, baseProfit * 0.7, baseProfit * 0.8, baseProfit * 0.9, baseProfit, baseProfit * 1.1, baseProfit * 1.2]
},
growth_bars: {
years: ['2021', '2022', '2023', '2024E', '2025E', '2026E'],
revenue_growth_pct: [16.0, 12.1, 10.8, 9.7, 8.9, 9.3]
revenue_growth_pct: [16.7, 14.3, 12.5, 11.1, 10.0, 9.1]
},
eps_trend: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
eps: [0.45, 0.52, 0.58, 0.65, 0.72, 0.80, 0.89]
eps: [0.45, 0.52, 0.60, 0.68, 0.76, 0.84, 0.92]
},
pe_peg_axes: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
pe: [22.2, 19.2, 17.2, 15.4, 13.9, 12.5, 11.2],
peg: [1.39, 1.59, 1.59, 1.42, 1.43, 1.40, 1.20]
pe: [22.2, 19.2, 16.7, 14.7, 13.2, 11.9, 10.9],
peg: [1.33, 1.34, 1.34, 1.32, 1.32, 1.31, 1.20]
},
detail_table: {
years: ['2020', '2021', '2022', '2023', '2024E', '2025E', '2026E'],
rows: [
{ '指标': '营业总收入(百万元)', '2020': 5000, '2021': 5800, '2022': 6500, '2023': 7200, '2024E': 7900, '2025E': 8600, '2026E': 9400 },
{ '指标': '营收增长率(%)', '2020': '-', '2021': 16.0, '2022': 12.1, '2023': 10.8, '2024E': 9.7, '2025E': 8.9, '2026E': 9.3 },
{ '指标': '归母净利润(百万元)', '2020': 450, '2021': 520, '2022': 580, '2023': 650, '2024E': 720, '2025E': 800, '2026E': 890 },
{ '指标': 'EPS(稀释,元)', '2020': 0.45, '2021': 0.52, '2022': 0.58, '2023': 0.65, '2024E': 0.72, '2025E': 0.80, '2026E': 0.89 },
{ '指标': '营业总收入(百万元)', '2020': baseRevenue * 0.6, '2021': baseRevenue * 0.7, '2022': baseRevenue * 0.8, '2023': baseRevenue * 0.9, '2024E': baseRevenue, '2025E': baseRevenue * 1.1, '2026E': baseRevenue * 1.2 },
{ '指标': '营收增长率(%)', '2020': '-', '2021': 16.7, '2022': 14.3, '2023': 12.5, '2024E': 11.1, '2025E': 10.0, '2026E': 9.1 },
{ '指标': '归母净利润(百万元)', '2020': baseProfit * 0.6, '2021': baseProfit * 0.7, '2022': baseProfit * 0.8, '2023': baseProfit * 0.9, '2024E': baseProfit, '2025E': baseProfit * 1.1, '2026E': baseProfit * 1.2 },
{ '指标': 'EPS(稀释,元)', '2020': 0.45, '2021': 0.52, '2022': 0.60, '2023': 0.68, '2024E': 0.76, '2025E': 0.84, '2026E': 0.92 },
{ '指标': 'ROE(%)', '2020': 12.5, '2021': 13.2, '2022': 13.8, '2023': 14.2, '2024E': 14.5, '2025E': 14.8, '2026E': 15.0 },
{ '指标': 'PE(倍)', '2020': 22.2, '2021': 19.2, '2022': 17.2, '2023': 15.4, '2024E': 13.9, '2025E': 12.5, '2026E': 11.2 }
{ '指标': 'PE(倍)', '2020': 22.2, '2021': 19.2, '2022': 16.7, '2023': 14.7, '2024E': 13.2, '2025E': 11.9, '2026E': 10.9 }
]
}
}

View File

@@ -43,12 +43,10 @@ export const companyHandlers = [
const { stockCode } = params;
const data = getCompanyData(stockCode);
// 直接返回 keyFactorsTimeline 对象(包含 key_factors 和 development_timeline
return HttpResponse.json({
success: true,
data: {
timeline: data.keyFactorsTimeline,
total: data.keyFactorsTimeline.length
}
data: data.keyFactorsTimeline
});
}),

View File

@@ -35,9 +35,9 @@ export const lazyComponents = {
// 公司相关模块
CompanyIndex: React.lazy(() => import('@views/Company')),
ForecastReport: React.lazy(() => import('@views/Company/ForecastReport')),
FinancialPanorama: React.lazy(() => import('@views/Company/FinancialPanorama')),
MarketDataView: React.lazy(() => import('@views/Company/MarketDataView')),
ForecastReport: React.lazy(() => import('@views/Company/components/ForecastReport')),
FinancialPanorama: React.lazy(() => import('@views/Company/components/FinancialPanorama')),
MarketDataView: React.lazy(() => import('@views/Company/components/MarketDataView')),
// Agent模块
AgentChat: React.lazy(() => import('@views/AgentChat')),

View File

@@ -340,6 +340,26 @@ const stockSlice = createSlice({
delete state.historicalEventsCache[eventId];
delete state.chainAnalysisCache[eventId];
delete state.expectationScores[eventId];
},
/**
* 乐观更新:添加自选股(同步)
*/
optimisticAddWatchlist: (state, action) => {
const { stockCode, stockName } = action.payload;
// 避免重复添加
const exists = state.watchlist.some(item => item.stock_code === stockCode);
if (!exists) {
state.watchlist.push({ stock_code: stockCode, stock_name: stockName || '' });
}
},
/**
* 乐观更新:移除自选股(同步)
*/
optimisticRemoveWatchlist: (state, action) => {
const { stockCode } = action.payload;
state.watchlist = state.watchlist.filter(item => item.stock_code !== stockCode);
}
},
extraReducers: (builder) => {
@@ -481,7 +501,9 @@ export const {
updateQuote,
updateQuotes,
clearQuotes,
clearEventCache
clearEventCache,
optimisticAddWatchlist,
optimisticRemoveWatchlist
} = stockSlice.actions;
export default stockSlice.reducer;

View File

@@ -0,0 +1,59 @@
// src/views/Company/components/CompanyHeader/SearchBar.js
// 股票搜索栏组件
import React from 'react';
import {
HStack,
Input,
Button,
InputGroup,
InputLeftElement,
} from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons';
/**
* 股票搜索栏组件
*
* @param {Object} props
* @param {string} props.inputCode - 输入框当前值
* @param {Function} props.onInputChange - 输入变化回调
* @param {Function} props.onSearch - 搜索按钮点击回调
* @param {Function} props.onKeyPress - 键盘事件回调
*/
const SearchBar = ({
inputCode,
onInputChange,
onSearch,
onKeyPress,
}) => {
return (
<HStack spacing={3}>
<InputGroup size="lg" maxW="300px">
<InputLeftElement pointerEvents="none">
<SearchIcon color="gray.400" />
</InputLeftElement>
<Input
placeholder="输入股票代码"
value={inputCode}
onChange={(e) => onInputChange(e.target.value)}
onKeyPress={onKeyPress}
borderRadius="md"
_focus={{
borderColor: 'blue.500',
boxShadow: '0 0 0 1px #3182ce',
}}
/>
</InputGroup>
<Button
colorScheme="blue"
size="lg"
onClick={onSearch}
leftIcon={<SearchIcon />}
>
查询
</Button>
</HStack>
);
};
export default SearchBar;

View File

@@ -0,0 +1,35 @@
// src/views/Company/components/CompanyHeader/WatchlistButton.js
// 自选股按钮组件
import React from 'react';
import { Button } from '@chakra-ui/react';
import { StarIcon } from '@chakra-ui/icons';
/**
* 自选股按钮组件
*
* @param {Object} props
* @param {boolean} props.isInWatchlist - 是否已在自选股中
* @param {boolean} props.isLoading - 是否正在加载
* @param {Function} props.onClick - 点击回调
*/
const WatchlistButton = ({
isInWatchlist,
isLoading,
onClick,
}) => {
return (
<Button
colorScheme={isInWatchlist ? 'yellow' : 'teal'}
variant={isInWatchlist ? 'solid' : 'outline'}
size="lg"
onClick={onClick}
leftIcon={<StarIcon />}
isLoading={isLoading}
>
{isInWatchlist ? '已关注' : '关注'}
</Button>
);
};
export default WatchlistButton;

View File

@@ -0,0 +1,94 @@
// src/views/Company/components/CompanyHeader/index.js
// 公司详情页面头部区域组件
import React from 'react';
import {
Card,
CardBody,
HStack,
VStack,
Heading,
Text,
Badge,
} from '@chakra-ui/react';
import SearchBar from './SearchBar';
import WatchlistButton from './WatchlistButton';
/**
* 公司详情页面头部区域组件
*
* 包含:
* - 页面标题和描述
* - 股票搜索栏
* - 自选股按钮
* - 当前股票代码显示
*
* @param {Object} props
* @param {string} props.stockCode - 当前股票代码
* @param {string} props.inputCode - 搜索输入框值
* @param {Function} props.onInputChange - 输入变化回调
* @param {Function} props.onSearch - 搜索回调
* @param {Function} props.onKeyPress - 键盘事件回调
* @param {boolean} props.isInWatchlist - 是否在自选股中
* @param {boolean} props.isWatchlistLoading - 自选股操作加载中
* @param {Function} props.onWatchlistToggle - 自选股切换回调
* @param {string} props.bgColor - 背景颜色
*/
const CompanyHeader = ({
stockCode,
inputCode,
onInputChange,
onSearch,
onKeyPress,
isInWatchlist,
isWatchlistLoading,
onWatchlistToggle,
bgColor,
}) => {
return (
<Card bg={bgColor} shadow="md">
<CardBody>
<HStack justify="space-between" align="center">
{/* 标题区域 */}
<VStack align="start" spacing={1}>
<Heading size="lg">个股详情</Heading>
<Text color="gray.600" fontSize="sm">
查看股票实时行情财务数据和盈利预测
</Text>
</VStack>
{/* 操作区域 */}
<HStack spacing={3}>
{/* 搜索栏 */}
<SearchBar
inputCode={inputCode}
onInputChange={onInputChange}
onSearch={onSearch}
onKeyPress={onKeyPress}
/>
{/* 自选股按钮 */}
<WatchlistButton
isInWatchlist={isInWatchlist}
isLoading={isWatchlistLoading}
onClick={onWatchlistToggle}
/>
</HStack>
</HStack>
{/* 当前股票信息 */}
<HStack mt={4} spacing={4}>
<Badge colorScheme="blue" fontSize="md" px={3} py={1}>
股票代码: {stockCode}
</Badge>
<Text fontSize="sm" color="gray.600">
更新时间: {new Date().toLocaleString()}
</Text>
</HStack>
</CardBody>
</Card>
);
};
export default CompanyHeader;

View File

@@ -36,8 +36,8 @@ import {
} from '@chakra-ui/icons';
import ReactECharts from 'echarts-for-react';
import { logger } from '../../utils/logger';
import { getApiBase } from '../../utils/apiConfig';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
// API配置
const API_BASE_URL = getApiBase();
@@ -1185,12 +1185,15 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
const links = [];
valueChainData.value_chain_flows.forEach(flow => {
// 检查 source 和 target 是否存在
if (!flow?.source?.node_name || !flow?.target?.node_name) return;
nodes.add(flow.source.node_name);
nodes.add(flow.target.node_name);
links.push({
source: flow.source.node_name,
target: flow.target.node_name,
value: parseFloat(flow.flow_metrics.flow_ratio) || 1,
value: parseFloat(flow.flow_metrics?.flow_ratio) || 1,
lineStyle: { color: 'source', opacity: 0.6 }
});
});
@@ -2426,7 +2429,9 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
<>
{event.keywords.slice(0, 4).map((keyword, kidx) => (
<Tag key={kidx} size="sm" colorScheme="cyan" variant="subtle">
{keyword}
{typeof keyword === 'string'
? keyword
: (keyword?.concept || keyword?.name || '未知')}
</Tag>
))}
</>

View File

@@ -0,0 +1,45 @@
// src/views/Company/components/CompanyTabs/TabNavigation.js
// Tab 导航组件 - 动态渲染 Tab 按钮
import React from 'react';
import {
TabList,
Tab,
HStack,
Icon,
Text,
} from '@chakra-ui/react';
import { COMPANY_TABS, TAB_SELECTED_STYLE } from '../../constants';
/**
* Tab 导航组件
*
* @param {Object} props
* @param {string} props.tabBg - Tab 列表背景色
* @param {string} props.activeBg - 激活状态背景色
*/
const TabNavigation = ({ tabBg, activeBg }) => {
return (
<TabList p={4} bg={tabBg}>
{COMPANY_TABS.map((tab, index) => (
<Tab
key={tab.key}
_selected={{
bg: activeBg,
color: 'white',
...TAB_SELECTED_STYLE,
}}
mr={index < COMPANY_TABS.length - 1 ? 2 : 0}
>
<HStack spacing={2}>
<Icon as={tab.icon} />
<Text>{tab.name}</Text>
</HStack>
</Tab>
))}
</TabList>
);
};
export default TabNavigation;

View File

@@ -0,0 +1,100 @@
// src/views/Company/components/CompanyTabs/index.js
// Tab 容器组件 - 管理 Tab 切换和内容渲染
import React, { useState } from 'react';
import {
Card,
CardBody,
Tabs,
TabPanels,
TabPanel,
Divider,
useColorModeValue,
} from '@chakra-ui/react';
import TabNavigation from './TabNavigation';
import { COMPANY_TABS, getTabNameByIndex } from '../../constants';
// 子组件导入Tab 内容组件)
import CompanyOverview from '../CompanyOverview';
import MarketDataView from '../MarketDataView';
import FinancialPanorama from '../FinancialPanorama';
import ForecastReport from '../ForecastReport';
/**
* Tab 组件映射
* key 与 COMPANY_TABS 中的 key 对应
*/
const TAB_COMPONENTS = {
overview: CompanyOverview,
market: MarketDataView,
financial: FinancialPanorama,
forecast: ForecastReport,
};
/**
* Tab 容器组件
*
* 功能:
* - 管理 Tab 切换状态
* - 动态渲染 Tab 导航和内容
* - 触发 Tab 变更追踪
*
* @param {Object} props
* @param {string} props.stockCode - 当前股票代码
* @param {Function} props.onTabChange - Tab 变更回调 (index, tabName, prevIndex) => void
* @param {string} props.bgColor - 背景颜色
*/
const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
const [currentIndex, setCurrentIndex] = useState(0);
// 主题相关颜色
const tabBg = useColorModeValue('gray.50', 'gray.700');
const activeBg = useColorModeValue('blue.500', 'blue.400');
/**
* 处理 Tab 切换
*/
const handleTabChange = (index) => {
const tabName = getTabNameByIndex(index);
// 触发追踪回调
onTabChange?.(index, tabName, currentIndex);
// 更新状态
setCurrentIndex(index);
};
return (
<Card bg={bgColor} shadow="lg">
<CardBody p={0}>
<Tabs
variant="soft-rounded"
colorScheme="blue"
size="lg"
index={currentIndex}
onChange={handleTabChange}
>
{/* Tab 导航 */}
<TabNavigation tabBg={tabBg} activeBg={activeBg} />
<Divider />
{/* Tab 内容面板 */}
<TabPanels>
{COMPANY_TABS.map((tab) => {
const Component = TAB_COMPONENTS[tab.key];
return (
<TabPanel key={tab.key} p={6}>
<Component stockCode={stockCode} />
</TabPanel>
);
})}
</TabPanels>
</Tabs>
</CardBody>
</Card>
);
};
export default CompanyTabs;

View File

@@ -1,6 +1,6 @@
// src/views/Company/FinancialPanorama.jsx
import React, { useState, useEffect, useMemo } from 'react';
import { logger } from '../../utils/logger';
import { logger } from '@utils/logger';
import {
Box,
Container,
@@ -75,7 +75,7 @@ import {
ArrowDownIcon,
} from '@chakra-ui/icons';
import ReactECharts from 'echarts-for-react';
import { financialService, formatUtils, chartUtils } from '../../services/financialService';
import { financialService, formatUtils, chartUtils } from '@services/financialService';
const FinancialPanorama = ({ stockCode: propStockCode }) => {
// 状态管理
@@ -84,7 +84,7 @@ const FinancialPanorama = ({ stockCode: propStockCode }) => {
const [error, setError] = useState(null);
const [selectedPeriods, setSelectedPeriods] = useState(8);
const [activeTab, setActiveTab] = useState(0);
// 财务数据状态
const [stockInfo, setStockInfo] = useState(null);
const [balanceSheet, setBalanceSheet] = useState([]);

View File

@@ -4,7 +4,7 @@ import { Box, Flex, Input, Button, SimpleGrid, HStack, Text, Skeleton, VStack }
import { Card, CardHeader, CardBody, Heading, Table, Thead, Tr, Th, Tbody, Td, Tag } from '@chakra-ui/react';
import { RepeatIcon } from '@chakra-ui/icons';
import ReactECharts from 'echarts-for-react';
import { stockService } from '../../services/eventService';
import { stockService } from '@services/eventService';
const ForecastReport = ({ stockCode: propStockCode }) => {
const [code, setCode] = useState(propStockCode || '600000');

View File

@@ -1,7 +1,7 @@
// src/views/Market/MarketDataPro.jsx
import React, { useState, useEffect, useMemo } from 'react';
import { logger } from '../../utils/logger';
import { getApiBase } from '../../utils/apiConfig';
import { logger } from '@utils/logger';
import { getApiBase } from '@utils/apiConfig';
import {
Box,
Container,
@@ -303,7 +303,7 @@ const MarketDataView = ({ stockCode: propStockCode }) => {
const [loading, setLoading] = useState(false);
const [activeTab, setActiveTab] = useState(0);
const [selectedPeriod, setSelectedPeriod] = useState(60);
// 数据状态
const [summary, setSummary] = useState(null);
const [tradeData, setTradeData] = useState([]);

View File

@@ -0,0 +1,53 @@
// src/views/Company/constants/index.js
// 公司详情页面常量配置
import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa';
/**
* Tab 配置
* @type {Array<{key: string, name: string, icon: React.ComponentType}>}
*/
export const COMPANY_TABS = [
{ key: 'overview', name: '公司概览', icon: FaInfoCircle },
{ key: 'market', name: '股票行情', icon: FaChartLine },
{ key: 'financial', name: '财务全景', icon: FaMoneyBillWave },
{ key: 'forecast', name: '盈利预测', icon: FaChartBar },
];
/**
* Tab 选中状态样式
*/
export const TAB_SELECTED_STYLE = {
transform: 'scale(1.02)',
transition: 'all 0.2s',
};
/**
* Toast 消息配置
*/
export const TOAST_MESSAGES = {
WATCHLIST_ADD: { title: '已加入自选', status: 'success', duration: 1500 },
WATCHLIST_REMOVE: { title: '已从自选移除', status: 'info', duration: 1500 },
WATCHLIST_ERROR: { title: '操作失败,请稍后重试', status: 'error', duration: 2000 },
INVALID_CODE: { title: '无效的股票代码', status: 'error', duration: 2000 },
LOGIN_REQUIRED: { title: '请先登录后再加入自选', status: 'warning', duration: 2000 },
};
/**
* 默认股票代码
*/
export const DEFAULT_STOCK_CODE = '000001';
/**
* URL 参数名
*/
export const URL_PARAM_NAME = 'scode';
/**
* 根据索引获取 Tab 名称
* @param {number} index - Tab 索引
* @returns {string} Tab 名称
*/
export const getTabNameByIndex = (index) => {
return COMPANY_TABS[index]?.name || 'Unknown';
};

View File

@@ -0,0 +1,90 @@
// src/views/Company/hooks/useCompanyStock.js
// 股票代码管理 Hook - 处理 URL 参数同步和搜索逻辑
import { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import { DEFAULT_STOCK_CODE, URL_PARAM_NAME } from '../constants';
/**
* 股票代码管理 Hook
*
* 功能:
* - 管理当前股票代码状态
* - 双向同步 URL 参数
* - 处理搜索输入和提交
*
* @param {Object} options - 配置选项
* @param {string} [options.defaultCode] - 默认股票代码
* @param {string} [options.paramName] - URL 参数名
* @param {Function} [options.onStockChange] - 股票代码变化回调 (newCode, prevCode) => void
* @returns {Object} 股票代码状态和操作方法
*/
export const useCompanyStock = (options = {}) => {
const {
defaultCode = DEFAULT_STOCK_CODE,
paramName = URL_PARAM_NAME,
onStockChange,
} = options;
const [searchParams, setSearchParams] = useSearchParams();
// 从 URL 参数初始化股票代码
const [stockCode, setStockCode] = useState(
searchParams.get(paramName) || defaultCode
);
// 输入框状态(未确认的输入)
const [inputCode, setInputCode] = useState(stockCode);
/**
* 监听 URL 参数变化,同步到本地状态
* 支持浏览器前进/后退按钮
*/
useEffect(() => {
const urlCode = searchParams.get(paramName);
if (urlCode && urlCode !== stockCode) {
setStockCode(urlCode);
setInputCode(urlCode);
}
}, [searchParams, paramName, stockCode]);
/**
* 执行搜索 - 更新 stockCode 和 URL
*/
const handleSearch = useCallback(() => {
const trimmedCode = inputCode?.trim();
if (trimmedCode && trimmedCode !== stockCode) {
// 触发变化回调(用于追踪)
onStockChange?.(trimmedCode, stockCode);
// 更新状态
setStockCode(trimmedCode);
// 更新 URL 参数
setSearchParams({ [paramName]: trimmedCode });
}
}, [inputCode, stockCode, paramName, setSearchParams, onStockChange]);
/**
* 处理键盘事件 - 回车键触发搜索
*/
const handleKeyPress = useCallback((e) => {
if (e.key === 'Enter') {
handleSearch();
}
}, [handleSearch]);
return {
// 状态
stockCode, // 当前确认的股票代码
inputCode, // 输入框中的值(未确认)
// 操作方法
setInputCode, // 更新输入框
handleSearch, // 执行搜索
handleKeyPress, // 处理回车键
};
};
export default useCompanyStock;

View File

@@ -0,0 +1,166 @@
// src/views/Company/hooks/useCompanyWatchlist.js
// 自选股管理 Hook - Company 页面专用,复用 Redux stockSlice
import { useEffect, useCallback, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useToast } from '@chakra-ui/react';
import { useAuth } from '@contexts/AuthContext';
import { logger } from '@utils/logger';
import {
loadWatchlist,
toggleWatchlist,
optimisticAddWatchlist,
optimisticRemoveWatchlist
} from '@store/slices/stockSlice';
import { TOAST_MESSAGES } from '../constants';
/**
* Company 页面自选股管理 Hook
*
* 功能:
* - 检查当前股票是否在自选股中
* - 提供添加/移除自选股功能
* - 与 Redux stockSlice 同步
*
* @param {Object} options - 配置选项
* @param {string} options.stockCode - 当前股票代码
* @param {Object} [options.tracking] - 追踪回调
* @param {Function} [options.tracking.onAdd] - 添加自选时的追踪回调
* @param {Function} [options.tracking.onRemove] - 移除自选时的追踪回调
* @returns {Object} 自选股状态和操作方法
*/
export const useCompanyWatchlist = ({ stockCode, tracking = {} } = {}) => {
const dispatch = useDispatch();
const toast = useToast();
const { isAuthenticated } = useAuth();
// 从 Redux 获取自选股列表
const watchlist = useSelector((state) => state.stock.watchlist);
const watchlistLoading = useSelector((state) => state.stock.loading.watchlist);
// 追踪是否已初始化(防止无限循环)
const hasInitializedRef = useRef(false);
/**
* 派生状态:判断当前股票是否在自选股中
* 使用 useMemo 避免重复计算
*/
const isInWatchlist = useMemo(() => {
if (!stockCode || !Array.isArray(watchlist)) {
return false;
}
// 标准化股票代码提取6位数字
const normalize = (code) => String(code || '').match(/(\d{6})/)?.[1] || '';
const targetCode = normalize(stockCode);
return watchlist.some((item) => normalize(item.stock_code) === targetCode);
}, [watchlist, stockCode]);
/**
* 初始化:加载自选股列表
* 使用 hasInitializedRef 防止无限循环(用户可能确实没有自选股)
*/
useEffect(() => {
if (!hasInitializedRef.current && isAuthenticated && !watchlistLoading) {
hasInitializedRef.current = true;
dispatch(loadWatchlist());
}
}, [isAuthenticated, watchlistLoading, dispatch]);
/**
* 切换自选股状态(乐观更新模式)
* 1. 立即更新 UI无 loading
* 2. 后台静默请求 API
* 3. 失败时回滚并提示
*/
const toggle = useCallback(async () => {
// 参数校验
if (!stockCode) {
logger.warn('useCompanyWatchlist', 'toggle', '无效的股票代码', { stockCode });
toast(TOAST_MESSAGES.INVALID_CODE);
return;
}
// 权限校验
if (!isAuthenticated) {
logger.warn('useCompanyWatchlist', 'toggle', '用户未登录', { stockCode });
toast(TOAST_MESSAGES.LOGIN_REQUIRED);
return;
}
// 标准化股票代码用于匹配
const normalize = (code) => String(code || '').match(/(\d{6})/)?.[1] || '';
const targetCode = normalize(stockCode);
// 从 watchlist 中找到原始 stock_code保持与后端数据结构一致
const matchedItem = watchlist.find(
item => normalize(item.stock_code) === targetCode
);
// 移除时使用原始 stock_code添加时使用传入的 stockCode
const codeForApi = isInWatchlist ? (matchedItem?.stock_code || stockCode) : stockCode;
// 保存当前状态用于回滚
const wasInWatchlist = isInWatchlist;
logger.debug('useCompanyWatchlist', '切换自选股(乐观更新)', {
stockCode,
codeForApi,
wasInWatchlist,
action: wasInWatchlist ? 'remove' : 'add',
});
// 1. 乐观更新:立即更新 UI不显示 loading
if (wasInWatchlist) {
dispatch(optimisticRemoveWatchlist({ stockCode: codeForApi }));
} else {
dispatch(optimisticAddWatchlist({ stockCode: codeForApi, stockName: matchedItem?.stock_name || '' }));
}
try {
// 2. 后台静默请求 API
await dispatch(
toggleWatchlist({
stockCode: codeForApi,
stockName: matchedItem?.stock_name || '',
isInWatchlist: wasInWatchlist,
})
).unwrap();
// 3. 成功:触发追踪回调(不显示 toast状态已更新
if (wasInWatchlist) {
tracking.onRemove?.(stockCode);
} else {
tracking.onAdd?.(stockCode);
}
} catch (error) {
// 4. 失败:回滚状态 + 显示错误提示
logger.error('useCompanyWatchlist', 'toggle', error, {
stockCode,
wasInWatchlist,
});
// 回滚操作
if (wasInWatchlist) {
// 之前在自选中,乐观删除了,现在要恢复
dispatch(optimisticAddWatchlist({ stockCode: codeForApi, stockName: matchedItem?.stock_name || '' }));
} else {
// 之前不在自选中,乐观添加了,现在要移除
dispatch(optimisticRemoveWatchlist({ stockCode: codeForApi }));
}
toast(TOAST_MESSAGES.WATCHLIST_ERROR);
}
}, [stockCode, isAuthenticated, isInWatchlist, watchlist, dispatch, toast, tracking]);
return {
// 状态
isInWatchlist, // 是否在自选股中
isLoading: watchlistLoading, // 仅初始加载时显示 loading乐观更新模式
// 操作方法
toggle, // 切换自选状态
};
};
export default useCompanyWatchlist;

View File

@@ -1,51 +1,40 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
Container,
Heading,
Card,
CardBody,
Tabs,
TabList,
TabPanels,
Tab,
TabPanel,
HStack,
VStack,
Input,
Button,
InputGroup,
InputLeftElement,
Text,
Badge,
Divider,
Icon,
useColorModeValue,
useColorMode,
IconButton,
useToast,
} from '@chakra-ui/react';
import { SearchIcon, MoonIcon, SunIcon, StarIcon } from '@chakra-ui/icons';
import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa';
import { useAuth } from '../../contexts/AuthContext';
import { logger } from '../../utils/logger';
import { getApiBase } from '../../utils/apiConfig';
import FinancialPanorama from './FinancialPanorama';
import ForecastReport from './ForecastReport';
import MarketDataView from './MarketDataView';
import CompanyOverview from './CompanyOverview';
// 导入 PostHog 追踪 Hook
// src/views/Company/index.js
// 公司详情页面入口 - 纯组合层
import React, { useEffect, useRef } from 'react';
import { Container, VStack, useColorModeValue } from '@chakra-ui/react';
// 自定义 Hooks
import { useCompanyStock } from './hooks/useCompanyStock';
import { useCompanyWatchlist } from './hooks/useCompanyWatchlist';
import { useCompanyEvents } from './hooks/useCompanyEvents';
const CompanyIndex = () => {
const [searchParams, setSearchParams] = useSearchParams();
const [stockCode, setStockCode] = useState(searchParams.get('scode') || '000001');
const [inputCode, setInputCode] = useState(stockCode);
const { colorMode, toggleColorMode } = useColorMode();
const toast = useToast();
const { isAuthenticated } = useAuth();
// 页面组件
import CompanyHeader from './components/CompanyHeader';
import CompanyTabs from './components/CompanyTabs';
// 🎯 PostHog 事件追踪
/**
* 公司详情页面
*
* 功能:
* - 股票搜索与代码管理
* - 自选股添加/移除
* - 多维度数据展示(概览、行情、财务、预测)
* - PostHog 事件追踪
*/
const CompanyIndex = () => {
const bgColor = useColorModeValue('white', 'gray.800');
// 1. 先获取股票代码(不带追踪回调)
const {
stockCode,
inputCode,
setInputCode,
handleSearch,
handleKeyPress,
} = useCompanyStock();
// 2. 再初始化事件追踪(传入 stockCode
const {
trackStockSearched,
trackTabChanged,
@@ -53,297 +42,53 @@ const CompanyIndex = () => {
trackWatchlistRemoved,
} = useCompanyEvents({ stockCode });
// Tab 索引状态(用于追踪 Tab 切换)
const [currentTabIndex, setCurrentTabIndex] = useState(0);
// 3. 自选股管理
const {
isInWatchlist,
isLoading: isWatchlistLoading,
toggle: handleWatchlistToggle,
} = useCompanyWatchlist({
stockCode,
tracking: {
onAdd: trackWatchlistAdded,
onRemove: trackWatchlistRemoved,
},
});
const bgColor = useColorModeValue('white', 'gray.800');
const tabBg = useColorModeValue('gray.50', 'gray.700');
const activeBg = useColorModeValue('blue.500', 'blue.400');
const [isInWatchlist, setIsInWatchlist] = useState(false);
const [isWatchlistLoading, setIsWatchlistLoading] = useState(false);
const loadWatchlistStatus = useCallback(async () => {
try {
const base = getApiBase();
const resp = await fetch(base + '/api/account/watchlist', {
credentials: 'include',
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' }
});
if (!resp.ok) {
setIsInWatchlist(false);
return;
}
const data = await resp.json();
const list = Array.isArray(data?.data) ? data.data : [];
const codes = new Set(list.map((item) => item.stock_code));
setIsInWatchlist(codes.has(stockCode));
} catch (e) {
setIsInWatchlist(false);
}
}, [stockCode]);
// 当URL参数变化时更新股票代码
// 4. 监听 stockCode 变化,触发搜索追踪
const prevStockCodeRef = useRef(stockCode);
useEffect(() => {
const scode = searchParams.get('scode');
if (scode && scode !== stockCode) {
setStockCode(scode);
setInputCode(scode);
if (stockCode !== prevStockCodeRef.current) {
trackStockSearched(stockCode, prevStockCodeRef.current);
prevStockCodeRef.current = stockCode;
}
}, [searchParams, stockCode]);
useEffect(() => {
loadWatchlistStatus();
}, [loadWatchlistStatus]);
const handleSearch = () => {
if (inputCode && inputCode !== stockCode) {
// 🎯 追踪股票搜索
trackStockSearched(inputCode, stockCode);
setStockCode(inputCode);
setSearchParams({ scode: inputCode });
}
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSearch();
}
};
const handleWatchlistToggle = async () => {
if (!stockCode) {
logger.warn('CompanyIndex', 'handleWatchlistToggle', '无效的股票代码', { stockCode });
toast({ title: '无效的股票代码', status: 'error', duration: 2000 });
return;
}
if (!isAuthenticated) {
logger.warn('CompanyIndex', 'handleWatchlistToggle', '用户未登录', { stockCode });
toast({ title: '请先登录后再加入自选', status: 'warning', duration: 2000 });
return;
}
try {
setIsWatchlistLoading(true);
const base = getApiBase();
if (isInWatchlist) {
logger.debug('CompanyIndex', '准备从自选移除', { stockCode });
const url = base + `/api/account/watchlist/${stockCode}`;
logger.api.request('DELETE', url, { stockCode });
const resp = await fetch(url, {
method: 'DELETE',
credentials: 'include'
});
logger.api.response('DELETE', url, resp.status);
if (!resp.ok) throw new Error('删除失败');
// 🎯 追踪移除自选
trackWatchlistRemoved(stockCode);
setIsInWatchlist(false);
toast({ title: '已从自选移除', status: 'info', duration: 1500 });
} else {
logger.debug('CompanyIndex', '准备添加到自选', { stockCode });
const url = base + '/api/account/watchlist';
const body = { stock_code: stockCode };
logger.api.request('POST', url, body);
const resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify(body)
});
logger.api.response('POST', url, resp.status);
if (!resp.ok) throw new Error('添加失败');
// 🎯 追踪加入自选
trackWatchlistAdded(stockCode);
setIsInWatchlist(true);
toast({ title: '已加入自选', status: 'success', duration: 1500 });
}
} catch (error) {
logger.error('CompanyIndex', 'handleWatchlistToggle', error, { stockCode, isInWatchlist });
toast({ title: '操作失败,请稍后重试', status: 'error', duration: 2000 });
} finally {
setIsWatchlistLoading(false);
}
};
}, [stockCode, trackStockSearched]);
return (
<Container maxW="container.xl" py={5}>
{/* 页面标题和股票搜索 */}
<VStack align="stretch" spacing={5}>
<Card bg={bgColor} shadow="md">
<CardBody>
<HStack justify="space-between" align="center">
<VStack align="start" spacing={1}>
<Heading size="lg">个股详情</Heading>
<Text color="gray.600" fontSize="sm">
查看股票实时行情财务数据和盈利预测
</Text>
</VStack>
<HStack spacing={3}>
<InputGroup size="lg" maxW="300px">
<InputLeftElement pointerEvents="none">
<SearchIcon color="gray.400" />
</InputLeftElement>
<Input
placeholder="输入股票代码"
value={inputCode}
onChange={(e) => setInputCode(e.target.value)}
onKeyPress={handleKeyPress}
borderRadius="md"
_focus={{
borderColor: 'blue.500',
boxShadow: '0 0 0 1px #3182ce'
}}
/>
</InputGroup>
<Button
colorScheme="blue"
size="lg"
onClick={handleSearch}
leftIcon={<SearchIcon />}
>
查询
</Button>
<Button
colorScheme={isInWatchlist ? 'yellow' : 'teal'}
variant={isInWatchlist ? 'solid' : 'outline'}
size="lg"
onClick={handleWatchlistToggle}
leftIcon={<StarIcon />}
isLoading={isWatchlistLoading}
>
{isInWatchlist ? '已在自选' : '加入自选'}
</Button>
<IconButton
icon={colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
onClick={toggleColorMode}
variant="outline"
colorScheme={colorMode === 'light' ? 'blue' : 'yellow'}
size="lg"
aria-label="Toggle color mode"
/>
</HStack>
</HStack>
{/* 当前股票信息 */}
<HStack mt={4} spacing={4}>
<Badge colorScheme="blue" fontSize="md" px={3} py={1}>
股票代码: {stockCode}
</Badge>
<Text fontSize="sm" color="gray.600">
更新时间: {new Date().toLocaleString()}
</Text>
</HStack>
</CardBody>
</Card>
{/* 数据展示区域 */}
<Card bg={bgColor} shadow="lg">
<CardBody p={0}>
<Tabs
variant="soft-rounded"
colorScheme="blue"
size="lg"
index={currentTabIndex}
onChange={(index) => {
const tabNames = ['公司概览', '股票行情', '财务全景', '盈利预测'];
// 🎯 追踪 Tab 切换
trackTabChanged(index, tabNames[index], currentTabIndex);
setCurrentTabIndex(index);
}}
>
<TabList p={4} bg={tabBg}>
<Tab
_selected={{
bg: activeBg,
color: 'white',
transform: 'scale(1.02)',
transition: 'all 0.2s'
}}
mr={2}
>
<HStack spacing={2}>
<Icon as={FaInfoCircle} />
<Text>公司概览</Text>
</HStack>
</Tab>
<Tab
_selected={{
bg: activeBg,
color: 'white',
transform: 'scale(1.02)',
transition: 'all 0.2s'
}}
mr={2}
>
<HStack spacing={2}>
<Icon as={FaChartLine} />
<Text>股票行情</Text>
</HStack>
</Tab>
<Tab
_selected={{
bg: activeBg,
color: 'white',
transform: 'scale(1.02)',
transition: 'all 0.2s'
}}
mr={2}
>
<HStack spacing={2}>
<Icon as={FaMoneyBillWave} />
<Text>财务全景</Text>
</HStack>
</Tab>
<Tab
_selected={{
bg: activeBg,
color: 'white',
transform: 'scale(1.02)',
transition: 'all 0.2s'
}}
>
<HStack spacing={2}>
<Icon as={FaChartBar} />
<Text>盈利预测</Text>
</HStack>
</Tab>
</TabList>
<Divider />
<TabPanels>
<TabPanel p={6}>
<CompanyOverview stockCode={stockCode} />
</TabPanel>
<TabPanel p={6}>
<MarketDataView stockCode={stockCode} />
</TabPanel>
<TabPanel p={6}>
<FinancialPanorama stockCode={stockCode} />
</TabPanel>
<TabPanel p={6}>
<ForecastReport stockCode={stockCode} />
</TabPanel>
</TabPanels>
</Tabs>
</CardBody>
</Card>
{/* 页面头部:标题、搜索、自选股按钮 */}
<CompanyHeader
stockCode={stockCode}
inputCode={inputCode}
onInputChange={setInputCode}
onSearch={handleSearch}
onKeyPress={handleKeyPress}
isInWatchlist={isInWatchlist}
isWatchlistLoading={isWatchlistLoading}
onWatchlistToggle={handleWatchlistToggle}
bgColor={bgColor}
/>
{/* Tab 切换区域:概览、行情、财务、预测 */}
<CompanyTabs
stockCode={stockCode}
onTabChange={trackTabChanged}
bgColor={bgColor}
/>
</VStack>
</Container>
);
};
export default CompanyIndex;