refactor(DynamicTracking): 拆分组件
- 新增 ForecastPanel: 业绩预告面板组件 - 新增 NewsPanel: 新闻面板组件 - 组件模块化重构
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// src/views/Company/components/DynamicTracking/components/ForecastPanel.js
|
||||
// 业绩预告面板
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
VStack,
|
||||
Card,
|
||||
CardBody,
|
||||
HStack,
|
||||
Badge,
|
||||
Text,
|
||||
Spinner,
|
||||
Center,
|
||||
} from '@chakra-ui/react';
|
||||
import { logger } from '@utils/logger';
|
||||
import { getApiBase } from '@utils/apiConfig';
|
||||
import { THEME } from '../../CompanyOverview/BasicInfoTab/config';
|
||||
|
||||
const API_BASE_URL = getApiBase();
|
||||
|
||||
const ForecastPanel = ({ stockCode }) => {
|
||||
const [forecast, setForecast] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const loadForecast = useCallback(async () => {
|
||||
if (!stockCode) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/api/stock/${stockCode}/forecast`
|
||||
);
|
||||
const result = await response.json();
|
||||
if (result.success && result.data) {
|
||||
setForecast(result.data);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('ForecastPanel', 'loadForecast', err, { stockCode });
|
||||
setForecast(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
useEffect(() => {
|
||||
loadForecast();
|
||||
}, [loadForecast]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Center py={10}>
|
||||
<Spinner size="lg" color={THEME.gold} />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!forecast?.forecasts?.length) {
|
||||
return (
|
||||
<Center py={10}>
|
||||
<Text color={THEME.textSecondary}>暂无业绩预告数据</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack spacing={4} align="stretch">
|
||||
{forecast.forecasts.map((item, idx) => (
|
||||
<Card key={idx} bg={THEME.cardBg} borderColor={THEME.border} borderWidth="1px">
|
||||
<CardBody>
|
||||
<HStack justify="space-between" mb={2}>
|
||||
<Badge colorScheme="blue">{item.forecast_type}</Badge>
|
||||
<Text fontSize="sm" color={THEME.textSecondary}>
|
||||
报告期: {item.report_date}
|
||||
</Text>
|
||||
</HStack>
|
||||
<Text mb={2} color={THEME.text}>{item.content}</Text>
|
||||
{item.reason && (
|
||||
<Text fontSize="sm" color={THEME.textSecondary}>
|
||||
{item.reason}
|
||||
</Text>
|
||||
)}
|
||||
{item.change_range?.lower && (
|
||||
<HStack mt={2}>
|
||||
<Text fontSize="sm" color={THEME.textSecondary}>预计变动范围:</Text>
|
||||
<Badge colorScheme="green">
|
||||
{item.change_range.lower}% ~ {item.change_range.upper}%
|
||||
</Badge>
|
||||
</HStack>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForecastPanel;
|
||||
@@ -0,0 +1,115 @@
|
||||
// src/views/Company/components/DynamicTracking/components/NewsPanel.js
|
||||
// 新闻动态面板(包装 NewsEventsTab)
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { logger } from '@utils/logger';
|
||||
import { getApiBase } from '@utils/apiConfig';
|
||||
import NewsEventsTab from '../../CompanyOverview/NewsEventsTab';
|
||||
|
||||
const API_BASE_URL = getApiBase();
|
||||
|
||||
const NewsPanel = ({ stockCode }) => {
|
||||
const [newsEvents, setNewsEvents] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pagination, setPagination] = useState({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
pages: 0,
|
||||
has_next: false,
|
||||
has_prev: false,
|
||||
});
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [stockName, setStockName] = useState('');
|
||||
|
||||
// 获取股票名称
|
||||
const fetchStockName = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/api/stock/${stockCode}/basic-info`
|
||||
);
|
||||
const result = await response.json();
|
||||
if (result.success && result.data) {
|
||||
const name = result.data.SECNAME || result.data.ORGNAME || stockCode;
|
||||
setStockName(name);
|
||||
return name;
|
||||
}
|
||||
return stockCode;
|
||||
} catch (err) {
|
||||
logger.error('NewsPanel', 'fetchStockName', err, { stockCode });
|
||||
return stockCode;
|
||||
}
|
||||
}, [stockCode]);
|
||||
|
||||
// 加载新闻事件
|
||||
const loadNewsEvents = useCallback(
|
||||
async (query, page = 1) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const searchTerm = query || stockName || stockCode;
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/api/events?q=${encodeURIComponent(searchTerm)}&page=${page}&per_page=10`
|
||||
);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
setNewsEvents(result.data || []);
|
||||
setPagination({
|
||||
page: result.pagination?.page || page,
|
||||
per_page: result.pagination?.per_page || 10,
|
||||
total: result.pagination?.total || 0,
|
||||
pages: result.pagination?.pages || 0,
|
||||
has_next: result.pagination?.has_next || false,
|
||||
has_prev: result.pagination?.has_prev || false,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('NewsPanel', 'loadNewsEvents', err, { stockCode });
|
||||
setNewsEvents([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[stockCode, stockName]
|
||||
);
|
||||
|
||||
// 首次加载
|
||||
useEffect(() => {
|
||||
const initLoad = async () => {
|
||||
if (stockCode) {
|
||||
const name = await fetchStockName();
|
||||
await loadNewsEvents(name, 1);
|
||||
}
|
||||
};
|
||||
initLoad();
|
||||
}, [stockCode, fetchStockName, loadNewsEvents]);
|
||||
|
||||
// 搜索处理
|
||||
const handleSearchChange = (value) => {
|
||||
setSearchQuery(value);
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
loadNewsEvents(searchQuery || stockName, 1);
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page) => {
|
||||
loadNewsEvents(searchQuery || stockName, page);
|
||||
};
|
||||
|
||||
return (
|
||||
<NewsEventsTab
|
||||
newsEvents={newsEvents}
|
||||
newsLoading={loading}
|
||||
newsPagination={pagination}
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSearch={handleSearch}
|
||||
onPageChange={handlePageChange}
|
||||
cardBg="white"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsPanel;
|
||||
@@ -0,0 +1,4 @@
|
||||
// src/views/Company/components/DynamicTracking/components/index.js
|
||||
|
||||
export { default as NewsPanel } from './NewsPanel';
|
||||
export { default as ForecastPanel } from './ForecastPanel';
|
||||
Reference in New Issue
Block a user