Compare commits
8 Commits
feature_bu
...
c1e10e6205
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1e10e6205 | ||
|
|
4954c58525 | ||
|
|
91bd581a5e | ||
|
|
258708fca0 | ||
|
|
90391729bb | ||
|
|
2148d319ad | ||
|
|
c61d58b0e3 | ||
|
|
ed1c7b9fa9 |
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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 }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
@@ -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')),
|
||||
|
||||
@@ -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;
|
||||
|
||||
59
src/views/Company/components/CompanyHeader/SearchBar.js
Normal file
59
src/views/Company/components/CompanyHeader/SearchBar.js
Normal 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;
|
||||
@@ -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;
|
||||
94
src/views/Company/components/CompanyHeader/index.js
Normal file
94
src/views/Company/components/CompanyHeader/index.js
Normal 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;
|
||||
@@ -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>
|
||||
))}
|
||||
</>
|
||||
45
src/views/Company/components/CompanyTabs/TabNavigation.js
Normal file
45
src/views/Company/components/CompanyTabs/TabNavigation.js
Normal 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;
|
||||
100
src/views/Company/components/CompanyTabs/index.js
Normal file
100
src/views/Company/components/CompanyTabs/index.js
Normal 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;
|
||||
@@ -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([]);
|
||||
@@ -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');
|
||||
@@ -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([]);
|
||||
53
src/views/Company/constants/index.js
Normal file
53
src/views/Company/constants/index.js
Normal 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';
|
||||
};
|
||||
90
src/views/Company/hooks/useCompanyStock.js
Normal file
90
src/views/Company/hooks/useCompanyStock.js
Normal 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;
|
||||
166
src/views/Company/hooks/useCompanyWatchlist.js
Normal file
166
src/views/Company/hooks/useCompanyWatchlist.js
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user