update pay function
This commit is contained in:
54
app.py
54
app.py
@@ -11679,6 +11679,60 @@ def get_value_chain_analysis(company_code):
|
|||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/company/value-chain/related-companies', methods=['GET'])
|
||||||
|
def get_related_companies_by_node():
|
||||||
|
"""
|
||||||
|
根据产业链节点名称查询相关公司
|
||||||
|
参数: node_name - 节点名称(如 "中芯国际"、"EDA/IP"等)
|
||||||
|
返回: 包含该节点的所有公司列表(去重)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
node_name = request.args.get('node_name')
|
||||||
|
|
||||||
|
if not node_name:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': '缺少必需参数 node_name'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# 查询包含该节点的所有公司
|
||||||
|
query = text("""
|
||||||
|
SELECT DISTINCT
|
||||||
|
n.company_code as stock_code,
|
||||||
|
s.SECNAME as stock_name,
|
||||||
|
s.ORGNAME as company_name
|
||||||
|
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
|
||||||
|
""")
|
||||||
|
|
||||||
|
with engine.connect() as conn:
|
||||||
|
result = conn.execute(query, {'node_name': node_name}).fetchall()
|
||||||
|
|
||||||
|
# 构建返回数据
|
||||||
|
companies = []
|
||||||
|
for row in result:
|
||||||
|
companies.append({
|
||||||
|
'stock_code': row.stock_code,
|
||||||
|
'stock_name': row.stock_name or row.stock_code,
|
||||||
|
'company_name': row.company_name
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'data': companies,
|
||||||
|
'total': len(companies),
|
||||||
|
'node_name': node_name
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/company/key-factors-timeline/<company_code>', methods=['GET'])
|
@app.route('/api/company/key-factors-timeline/<company_code>', methods=['GET'])
|
||||||
def get_key_factors_timeline(company_code):
|
def get_key_factors_timeline(company_code):
|
||||||
"""获取公司关键因素和时间线数据"""
|
"""获取公司关键因素和时间线数据"""
|
||||||
|
|||||||
@@ -793,6 +793,11 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
|||||||
const [announcements, setAnnouncements] = useState([]);
|
const [announcements, setAnnouncements] = useState([]);
|
||||||
const [disclosureSchedule, setDisclosureSchedule] = useState([]);
|
const [disclosureSchedule, setDisclosureSchedule] = useState([]);
|
||||||
|
|
||||||
|
// 新闻动态数据
|
||||||
|
const [newsEvents, setNewsEvents] = useState([]);
|
||||||
|
const [newsLoading, setNewsLoading] = useState(false);
|
||||||
|
const [newsSearchQuery, setNewsSearchQuery] = useState('');
|
||||||
|
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -880,6 +885,58 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
|||||||
}
|
}
|
||||||
}, [stockCode]);
|
}, [stockCode]);
|
||||||
|
|
||||||
|
// 加载新闻事件
|
||||||
|
const loadNewsEvents = async (searchQuery = '') => {
|
||||||
|
setNewsLoading(true);
|
||||||
|
try {
|
||||||
|
// 构建查询参数
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
per_page: '20',
|
||||||
|
sort: 'new',
|
||||||
|
include_creator: 'false',
|
||||||
|
include_stats: 'true'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果有搜索关键词,添加搜索参数
|
||||||
|
if (searchQuery) {
|
||||||
|
params.append('q', searchQuery);
|
||||||
|
params.append('search_fields', 'title,description,content');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有股票代码,添加相关筛选(这里假设事件表有stock_code字段)
|
||||||
|
// 如果没有直接的stock_code字段,可以通过tags或keywords搜索
|
||||||
|
if (basicInfo?.SECNAME) {
|
||||||
|
params.append('keywords', basicInfo.SECNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/events?${params.toString()}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success !== false && data.events) {
|
||||||
|
setNewsEvents(data.events);
|
||||||
|
} else {
|
||||||
|
setNewsEvents([]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('CompanyOverview', 'loadNewsEvents', err, { stockCode, searchQuery });
|
||||||
|
setNewsEvents([]);
|
||||||
|
} finally {
|
||||||
|
setNewsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当基本信息加载完成后,加载新闻事件
|
||||||
|
useEffect(() => {
|
||||||
|
if (basicInfo) {
|
||||||
|
loadNewsEvents();
|
||||||
|
}
|
||||||
|
}, [basicInfo]);
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleNewsSearch = () => {
|
||||||
|
loadNewsEvents(newsSearchQuery);
|
||||||
|
};
|
||||||
|
|
||||||
// 管理层职位分类
|
// 管理层职位分类
|
||||||
const getManagementByCategory = () => {
|
const getManagementByCategory = () => {
|
||||||
const categories = {
|
const categories = {
|
||||||
@@ -2131,22 +2188,166 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => {
|
|||||||
<Card bg={cardBg} shadow="md">
|
<Card bg={cardBg} shadow="md">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<VStack spacing={4} align="stretch">
|
<VStack spacing={4} align="stretch">
|
||||||
<InputGroup>
|
{/* 搜索框 */}
|
||||||
<InputLeftElement pointerEvents="none">
|
<HStack>
|
||||||
<SearchIcon color="gray.400" />
|
<InputGroup>
|
||||||
</InputLeftElement>
|
<InputLeftElement pointerEvents="none">
|
||||||
<Input placeholder="搜索相关新闻..." />
|
<SearchIcon color="gray.400" />
|
||||||
</InputGroup>
|
</InputLeftElement>
|
||||||
|
<Input
|
||||||
|
placeholder="搜索相关新闻..."
|
||||||
|
value={newsSearchQuery}
|
||||||
|
onChange={(e) => setNewsSearchQuery(e.target.value)}
|
||||||
|
onKeyPress={(e) => e.key === 'Enter' && handleNewsSearch()}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
onClick={handleNewsSearch}
|
||||||
|
isLoading={newsLoading}
|
||||||
|
>
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
<Center h="300px">
|
{/* 新闻列表 */}
|
||||||
<VStack spacing={3}>
|
{newsLoading ? (
|
||||||
<Icon as={FaNewspaper} boxSize={16} color="gray.300" />
|
<Center h="300px">
|
||||||
<Text color="gray.500">新闻接口待接入</Text>
|
<VStack spacing={3}>
|
||||||
<Text fontSize="sm" color="gray.400">
|
<Spinner size="xl" color="blue.500" thickness="4px" />
|
||||||
即将展示最新的公司新闻动态
|
<Text>正在加载新闻...</Text>
|
||||||
</Text>
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
) : newsEvents.length > 0 ? (
|
||||||
|
<VStack spacing={3} align="stretch">
|
||||||
|
{newsEvents.map((event, idx) => {
|
||||||
|
const importanceColor = {
|
||||||
|
'S': 'red',
|
||||||
|
'A': 'orange',
|
||||||
|
'B': 'yellow',
|
||||||
|
'C': 'green'
|
||||||
|
}[event.importance] || 'gray';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={idx}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{ bg: 'gray.50', shadow: 'md' }}
|
||||||
|
transition="all 0.2s"
|
||||||
|
>
|
||||||
|
<CardBody p={4}>
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
<HStack justify="space-between" align="start">
|
||||||
|
<VStack align="start" spacing={1} flex={1}>
|
||||||
|
<HStack>
|
||||||
|
<Icon as={FaNewspaper} color="blue.500" boxSize={4} />
|
||||||
|
<Text fontWeight="bold" fontSize="md" noOfLines={2}>
|
||||||
|
{event.title}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<HStack spacing={2} flexWrap="wrap">
|
||||||
|
{event.importance && (
|
||||||
|
<Badge colorScheme={importanceColor} size="sm">
|
||||||
|
重要度: {event.importance}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{event.event_type && (
|
||||||
|
<Badge colorScheme="blue" size="sm">
|
||||||
|
{event.event_type}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{event.tags && event.tags.length > 0 && (
|
||||||
|
<>
|
||||||
|
{event.tags.slice(0, 3).map((tag, tidx) => (
|
||||||
|
<Tag key={tidx} size="sm" variant="subtle">
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
<VStack align="end" spacing={1}>
|
||||||
|
<Text fontSize="xs" color="gray.500">
|
||||||
|
{event.created_at ? new Date(event.created_at).toLocaleDateString('zh-CN') : ''}
|
||||||
|
</Text>
|
||||||
|
{event.view_count && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Icon as={FaEye} boxSize={3} color="gray.400" />
|
||||||
|
<Text fontSize="xs" color="gray.500">
|
||||||
|
{event.view_count}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{event.description && (
|
||||||
|
<Text fontSize="sm" color="gray.600" noOfLines={2}>
|
||||||
|
{event.description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(event.avg_return_5 || event.avg_return_10 || event.avg_return_20) && (
|
||||||
|
<HStack spacing={4} pt={2} borderTop="1px" borderColor="gray.200">
|
||||||
|
<Text fontSize="xs" color="gray.500">平均收益率:</Text>
|
||||||
|
{event.avg_return_5 !== undefined && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Text fontSize="xs" color="gray.500">5日:</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={event.avg_return_5 > 0 ? 'red.500' : 'green.500'}
|
||||||
|
>
|
||||||
|
{event.avg_return_5 > 0 ? '+' : ''}{event.avg_return_5}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
{event.avg_return_10 !== undefined && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Text fontSize="xs" color="gray.500">10日:</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={event.avg_return_10 > 0 ? 'red.500' : 'green.500'}
|
||||||
|
>
|
||||||
|
{event.avg_return_10 > 0 ? '+' : ''}{event.avg_return_10}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
{event.avg_return_20 !== undefined && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<Text fontSize="xs" color="gray.500">20日:</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={event.avg_return_20 > 0 ? 'red.500' : 'green.500'}
|
||||||
|
>
|
||||||
|
{event.avg_return_20 > 0 ? '+' : ''}{event.avg_return_20}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
) : (
|
||||||
|
<Center h="300px">
|
||||||
|
<VStack spacing={3}>
|
||||||
|
<Icon as={FaNewspaper} boxSize={16} color="gray.300" />
|
||||||
|
<Text color="gray.500">暂无相关新闻</Text>
|
||||||
|
<Text fontSize="sm" color="gray.400">
|
||||||
|
{newsSearchQuery ? '尝试修改搜索关键词' : '该公司暂无新闻动态'}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user