refactor: Company 目录结构重组 - Tab 内容组件文件夹化
- 将 4 个 Tab 内容组件移动到 components/ 目录下 - CompanyOverview.js → components/CompanyOverview/index.js - MarketDataView.js → components/MarketDataView/index.js - FinancialPanorama.js → components/FinancialPanorama/index.js - ForecastReport.js → components/ForecastReport/index.js - 更新 CompanyTabs/index.js 导入路径 - 更新 routes/lazy-components.js 路由路径 - 修复组件内相对路径导入,改用 @utils/@services 别名 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -35,9 +35,9 @@ export const lazyComponents = {
|
|||||||
|
|
||||||
// 公司相关模块
|
// 公司相关模块
|
||||||
CompanyIndex: React.lazy(() => import('@views/Company')),
|
CompanyIndex: React.lazy(() => import('@views/Company')),
|
||||||
ForecastReport: React.lazy(() => import('@views/Company/ForecastReport')),
|
ForecastReport: React.lazy(() => import('@views/Company/components/ForecastReport')),
|
||||||
FinancialPanorama: React.lazy(() => import('@views/Company/FinancialPanorama')),
|
FinancialPanorama: React.lazy(() => import('@views/Company/components/FinancialPanorama')),
|
||||||
MarketDataView: React.lazy(() => import('@views/Company/MarketDataView')),
|
MarketDataView: React.lazy(() => import('@views/Company/components/MarketDataView')),
|
||||||
|
|
||||||
// Agent模块
|
// Agent模块
|
||||||
AgentChat: React.lazy(() => import('@views/AgentChat')),
|
AgentChat: React.lazy(() => import('@views/AgentChat')),
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ import {
|
|||||||
} from '@chakra-ui/icons';
|
} from '@chakra-ui/icons';
|
||||||
|
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
import { getApiBase } from '../../utils/apiConfig';
|
import { getApiBase } from '@utils/apiConfig';
|
||||||
|
|
||||||
// API配置
|
// API配置
|
||||||
const API_BASE_URL = getApiBase();
|
const API_BASE_URL = getApiBase();
|
||||||
@@ -15,11 +15,11 @@ import {
|
|||||||
import TabNavigation from './TabNavigation';
|
import TabNavigation from './TabNavigation';
|
||||||
import { COMPANY_TABS, getTabNameByIndex } from '../../constants';
|
import { COMPANY_TABS, getTabNameByIndex } from '../../constants';
|
||||||
|
|
||||||
// 子组件导入
|
// 子组件导入(Tab 内容组件)
|
||||||
import FinancialPanorama from '../../FinancialPanorama';
|
import CompanyOverview from '../CompanyOverview';
|
||||||
import ForecastReport from '../../ForecastReport';
|
import MarketDataView from '../MarketDataView';
|
||||||
import MarketDataView from '../../MarketDataView';
|
import FinancialPanorama from '../FinancialPanorama';
|
||||||
import CompanyOverview from '../../CompanyOverview';
|
import ForecastReport from '../ForecastReport';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab 组件映射
|
* Tab 组件映射
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// src/views/Company/FinancialPanorama.jsx
|
// src/views/Company/FinancialPanorama.jsx
|
||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
@@ -75,7 +75,7 @@ import {
|
|||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
} from '@chakra-ui/icons';
|
} from '@chakra-ui/icons';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { financialService, formatUtils, chartUtils } from '../../services/financialService';
|
import { financialService, formatUtils, chartUtils } from '@services/financialService';
|
||||||
|
|
||||||
const FinancialPanorama = ({ stockCode: propStockCode }) => {
|
const FinancialPanorama = ({ stockCode: propStockCode }) => {
|
||||||
// 状态管理
|
// 状态管理
|
||||||
@@ -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 { Card, CardHeader, CardBody, Heading, Table, Thead, Tr, Th, Tbody, Td, Tag } from '@chakra-ui/react';
|
||||||
import { RepeatIcon } from '@chakra-ui/icons';
|
import { RepeatIcon } from '@chakra-ui/icons';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
import { stockService } from '../../services/eventService';
|
import { stockService } from '@services/eventService';
|
||||||
|
|
||||||
const ForecastReport = ({ stockCode: propStockCode }) => {
|
const ForecastReport = ({ stockCode: propStockCode }) => {
|
||||||
const [code, setCode] = useState(propStockCode || '600000');
|
const [code, setCode] = useState(propStockCode || '600000');
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/views/Market/MarketDataPro.jsx
|
// src/views/Market/MarketDataPro.jsx
|
||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
import { getApiBase } from '../../utils/apiConfig';
|
import { getApiBase } from '@utils/apiConfig';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
@@ -1,51 +1,40 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
// src/views/Company/index.js
|
||||||
import { useSearchParams } from 'react-router-dom';
|
// 公司详情页面入口 - 纯组合层
|
||||||
import {
|
|
||||||
Container,
|
import React, { useEffect, useRef } from 'react';
|
||||||
Heading,
|
import { Container, VStack, useColorModeValue } from '@chakra-ui/react';
|
||||||
Card,
|
|
||||||
CardBody,
|
// 自定义 Hooks
|
||||||
Tabs,
|
import { useCompanyStock } from './hooks/useCompanyStock';
|
||||||
TabList,
|
import { useCompanyWatchlist } from './hooks/useCompanyWatchlist';
|
||||||
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
|
|
||||||
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
import { useCompanyEvents } from './hooks/useCompanyEvents';
|
||||||
|
|
||||||
const CompanyIndex = () => {
|
// 页面组件
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
import CompanyHeader from './components/CompanyHeader';
|
||||||
const [stockCode, setStockCode] = useState(searchParams.get('scode') || '000001');
|
import CompanyTabs from './components/CompanyTabs';
|
||||||
const [inputCode, setInputCode] = useState(stockCode);
|
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
|
||||||
const toast = useToast();
|
|
||||||
const { isAuthenticated } = useAuth();
|
|
||||||
|
|
||||||
// 🎯 PostHog 事件追踪
|
/**
|
||||||
|
* 公司详情页面
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - 股票搜索与代码管理
|
||||||
|
* - 自选股添加/移除
|
||||||
|
* - 多维度数据展示(概览、行情、财务、预测)
|
||||||
|
* - PostHog 事件追踪
|
||||||
|
*/
|
||||||
|
const CompanyIndex = () => {
|
||||||
|
const bgColor = useColorModeValue('white', 'gray.800');
|
||||||
|
|
||||||
|
// 1. 先获取股票代码(不带追踪回调)
|
||||||
|
const {
|
||||||
|
stockCode,
|
||||||
|
inputCode,
|
||||||
|
setInputCode,
|
||||||
|
handleSearch,
|
||||||
|
handleKeyPress,
|
||||||
|
} = useCompanyStock();
|
||||||
|
|
||||||
|
// 2. 再初始化事件追踪(传入 stockCode)
|
||||||
const {
|
const {
|
||||||
trackStockSearched,
|
trackStockSearched,
|
||||||
trackTabChanged,
|
trackTabChanged,
|
||||||
@@ -53,297 +42,53 @@ const CompanyIndex = () => {
|
|||||||
trackWatchlistRemoved,
|
trackWatchlistRemoved,
|
||||||
} = useCompanyEvents({ stockCode });
|
} = useCompanyEvents({ stockCode });
|
||||||
|
|
||||||
// Tab 索引状态(用于追踪 Tab 切换)
|
// 3. 自选股管理
|
||||||
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
const {
|
||||||
|
isInWatchlist,
|
||||||
|
isLoading: isWatchlistLoading,
|
||||||
|
toggle: handleWatchlistToggle,
|
||||||
|
} = useCompanyWatchlist({
|
||||||
|
stockCode,
|
||||||
|
tracking: {
|
||||||
|
onAdd: trackWatchlistAdded,
|
||||||
|
onRemove: trackWatchlistRemoved,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const bgColor = useColorModeValue('white', 'gray.800');
|
// 4. 监听 stockCode 变化,触发搜索追踪
|
||||||
const tabBg = useColorModeValue('gray.50', 'gray.700');
|
const prevStockCodeRef = useRef(stockCode);
|
||||||
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参数变化时更新股票代码
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const scode = searchParams.get('scode');
|
if (stockCode !== prevStockCodeRef.current) {
|
||||||
if (scode && scode !== stockCode) {
|
trackStockSearched(stockCode, prevStockCodeRef.current);
|
||||||
setStockCode(scode);
|
prevStockCodeRef.current = stockCode;
|
||||||
setInputCode(scode);
|
|
||||||
}
|
}
|
||||||
}, [searchParams, stockCode]);
|
}, [stockCode, trackStockSearched]);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxW="container.xl" py={5}>
|
<Container maxW="container.xl" py={5}>
|
||||||
{/* 页面标题和股票搜索 */}
|
|
||||||
<VStack align="stretch" spacing={5}>
|
<VStack align="stretch" spacing={5}>
|
||||||
<Card bg={bgColor} shadow="md">
|
{/* 页面头部:标题、搜索、自选股按钮 */}
|
||||||
<CardBody>
|
<CompanyHeader
|
||||||
<HStack justify="space-between" align="center">
|
stockCode={stockCode}
|
||||||
<VStack align="start" spacing={1}>
|
inputCode={inputCode}
|
||||||
<Heading size="lg">个股详情</Heading>
|
onInputChange={setInputCode}
|
||||||
<Text color="gray.600" fontSize="sm">
|
onSearch={handleSearch}
|
||||||
查看股票实时行情、财务数据和盈利预测
|
onKeyPress={handleKeyPress}
|
||||||
</Text>
|
isInWatchlist={isInWatchlist}
|
||||||
</VStack>
|
isWatchlistLoading={isWatchlistLoading}
|
||||||
|
onWatchlistToggle={handleWatchlistToggle}
|
||||||
|
bgColor={bgColor}
|
||||||
|
/>
|
||||||
|
|
||||||
<HStack spacing={3}>
|
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
||||||
<InputGroup size="lg" maxW="300px">
|
<CompanyTabs
|
||||||
<InputLeftElement pointerEvents="none">
|
stockCode={stockCode}
|
||||||
<SearchIcon color="gray.400" />
|
onTabChange={trackTabChanged}
|
||||||
</InputLeftElement>
|
bgColor={bgColor}
|
||||||
<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>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CompanyIndex;
|
export default CompanyIndex;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user