diff --git a/app.py b/app.py index a7818e23..43eda8a5 100755 --- a/app.py +++ b/app.py @@ -11682,9 +11682,9 @@ def get_value_chain_analysis(company_code): @app.route('/api/company/value-chain/related-companies', methods=['GET']) def get_related_companies_by_node(): """ - 根据产业链节点名称查询相关公司 + 根据产业链节点名称查询相关公司(结合nodes和flows表) 参数: node_name - 节点名称(如 "中芯国际"、"EDA/IP"等) - 返回: 包含该节点的所有公司列表(去重) + 返回: 包含该节点的所有公司列表,附带节点层级、类型、关系描述等信息 """ try: node_name = request.args.get('node_name') @@ -11695,29 +11695,88 @@ def get_related_companies_by_node(): 'error': '缺少必需参数 node_name' }), 400 - # 查询包含该节点的所有公司 + # 查询包含该节点的所有公司及其节点信息 query = text(""" SELECT DISTINCT n.company_code as stock_code, s.SECNAME as stock_name, - s.ORGNAME as company_name + s.ORGNAME as company_name, + n.node_level, + n.node_type, + n.node_description, + n.importance_score, + n.market_share, + n.dependency_degree FROM company_value_chain_nodes n LEFT JOIN ea_stocklist s ON n.company_code = s.SECCODE WHERE n.node_name = :node_name - ORDER BY n.company_code + ORDER BY n.importance_score DESC, n.company_code """) with engine.connect() as conn: - result = conn.execute(query, {'node_name': node_name}).fetchall() + nodes_result = conn.execute(query, {'node_name': node_name}).fetchall() # 构建返回数据 companies = [] - for row in result: - companies.append({ + for row in nodes_result: + company_data = { 'stock_code': row.stock_code, 'stock_name': row.stock_name or row.stock_code, - 'company_name': row.company_name - }) + 'company_name': row.company_name, + 'node_info': { + 'node_level': row.node_level, + 'node_type': row.node_type, + 'node_description': row.node_description, + 'importance_score': row.importance_score, + 'market_share': format_decimal(row.market_share), + 'dependency_degree': format_decimal(row.dependency_degree) + }, + 'relationships': [] + } + + # 查询该节点在该公司产业链中的流向关系 + flows_query = text(""" + SELECT + source_node, + source_type, + source_level, + target_node, + target_type, + target_level, + flow_type, + relationship_desc, + flow_value, + flow_ratio + FROM company_value_chain_flows + WHERE company_code = :company_code + AND (source_node = :node_name OR target_node = :node_name) + ORDER BY flow_ratio DESC + LIMIT 5 + """) + + with engine.connect() as conn: + flows_result = conn.execute(flows_query, { + 'company_code': row.stock_code, + 'node_name': node_name + }).fetchall() + + # 添加流向关系信息 + for flow in flows_result: + # 判断节点在流向中的角色 + is_source = (flow.source_node == node_name) + + relationship = { + 'role': 'source' if is_source else 'target', + 'connected_node': flow.target_node if is_source else flow.source_node, + 'connected_type': flow.target_type if is_source else flow.source_type, + 'connected_level': flow.target_level if is_source else flow.source_level, + 'flow_type': flow.flow_type, + 'relationship_desc': flow.relationship_desc, + 'flow_ratio': format_decimal(flow.flow_ratio) + } + company_data['relationships'].append(relationship) + + companies.append(company_data) return jsonify({ 'success': True, diff --git a/src/views/Company/CompanyOverview.js b/src/views/Company/CompanyOverview.js index e4ab6372..0f069ef6 100644 --- a/src/views/Company/CompanyOverview.js +++ b/src/views/Company/CompanyOverview.js @@ -468,34 +468,126 @@ const ValueChainNodeCard = ({ node, isCompany = false, level = 0 }) => { ) : relatedCompanies.length > 0 ? ( - - {relatedCompanies.map((company, idx) => ( - - - - - - {company.stock_name} - {company.stock_code} + + {relatedCompanies.map((company, idx) => { + // 获取节点层级标签 + const getLevelLabel = (level) => { + if (level < 0) return { text: '上游', color: 'orange' }; + if (level === 0) return { text: '核心', color: 'blue' }; + if (level > 0) return { text: '下游', color: 'green' }; + return { text: '未知', color: 'gray' }; + }; + + const levelInfo = getLevelLabel(company.node_info?.node_level); + + return ( + + + + {/* 公司基本信息 */} + + + + {company.stock_name} + {company.stock_code} + + {levelInfo.text} + + {company.node_info?.node_type && ( + + {company.node_info.node_type} + + )} + + {company.company_name && ( + + {company.company_name} + + )} + + } + variant="ghost" + colorScheme="blue" + onClick={() => { + window.location.href = `/company?stock_code=${company.stock_code}`; + }} + aria-label="查看公司详情" + /> - {company.company_name && ( - {company.company_name} + + {/* 节点描述 */} + {company.node_info?.node_description && ( + + {company.node_info.node_description} + + )} + + {/* 节点指标 */} + {(company.node_info?.importance_score || company.node_info?.market_share || company.node_info?.dependency_degree) && ( + + {company.node_info.importance_score && ( + + 重要度: + {company.node_info.importance_score} + + )} + {company.node_info.market_share && ( + + 市场份额: + {company.node_info.market_share}% + + )} + {company.node_info.dependency_degree && ( + + 依赖度: + {company.node_info.dependency_degree}% + + )} + + )} + + {/* 流向关系 */} + {company.relationships && company.relationships.length > 0 && ( + + + 产业链关系: + + + {company.relationships.map((rel, ridx) => ( + + + + {rel.role === 'source' ? '流向' : '来自'} + + {rel.connected_node} + + + {rel.relationship_desc && ( + + {rel.relationship_desc} + + )} + {rel.flow_ratio && ( + + {rel.flow_ratio}% + + )} + + ))} + + )} - } - variant="ghost" - colorScheme="blue" - onClick={() => { - window.location.href = `/company?stock_code=${company.stock_code}`; - }} - aria-label="查看公司详情" - /> - - - - ))} + + + ); + })} ) : (