From 4954c585259204a421225a86411dbb9d9aa3e79a Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Tue, 9 Dec 2025 15:31:58 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20Company=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E9=87=8D=E7=BB=84=20-=20Tab=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E7=BB=84=E4=BB=B6=E6=96=87=E4=BB=B6=E5=A4=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 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 --- src/routes/lazy-components.js | 6 +- .../CompanyOverview/index.js} | 4 +- .../Company/components/CompanyTabs/index.js | 10 +- .../FinancialPanorama/index.js} | 6 +- .../ForecastReport/index.js} | 2 +- .../MarketDataView/index.js} | 6 +- src/views/Company/index.js | 397 ++++-------------- 7 files changed, 88 insertions(+), 343 deletions(-) rename src/views/Company/{CompanyOverview.js => components/CompanyOverview/index.js} (99%) rename src/views/Company/{FinancialPanorama.js => components/FinancialPanorama/index.js} (99%) rename src/views/Company/{ForecastReport.js => components/ForecastReport/index.js} (99%) rename src/views/Company/{MarketDataView.js => components/MarketDataView/index.js} (99%) diff --git a/src/routes/lazy-components.js b/src/routes/lazy-components.js index ada81708..37fed848 100644 --- a/src/routes/lazy-components.js +++ b/src/routes/lazy-components.js @@ -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')), diff --git a/src/views/Company/CompanyOverview.js b/src/views/Company/components/CompanyOverview/index.js similarity index 99% rename from src/views/Company/CompanyOverview.js rename to src/views/Company/components/CompanyOverview/index.js index 39d7d85b..9254bc3c 100644 --- a/src/views/Company/CompanyOverview.js +++ b/src/views/Company/components/CompanyOverview/index.js @@ -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(); diff --git a/src/views/Company/components/CompanyTabs/index.js b/src/views/Company/components/CompanyTabs/index.js index 10ae3a2e..4f0d3b1b 100644 --- a/src/views/Company/components/CompanyTabs/index.js +++ b/src/views/Company/components/CompanyTabs/index.js @@ -15,11 +15,11 @@ import { import TabNavigation from './TabNavigation'; import { COMPANY_TABS, getTabNameByIndex } from '../../constants'; -// 子组件导入 -import FinancialPanorama from '../../FinancialPanorama'; -import ForecastReport from '../../ForecastReport'; -import MarketDataView from '../../MarketDataView'; -import CompanyOverview from '../../CompanyOverview'; +// 子组件导入(Tab 内容组件) +import CompanyOverview from '../CompanyOverview'; +import MarketDataView from '../MarketDataView'; +import FinancialPanorama from '../FinancialPanorama'; +import ForecastReport from '../ForecastReport'; /** * Tab 组件映射 diff --git a/src/views/Company/FinancialPanorama.js b/src/views/Company/components/FinancialPanorama/index.js similarity index 99% rename from src/views/Company/FinancialPanorama.js rename to src/views/Company/components/FinancialPanorama/index.js index 4bd626bb..0b3568e7 100644 --- a/src/views/Company/FinancialPanorama.js +++ b/src/views/Company/components/FinancialPanorama/index.js @@ -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([]); diff --git a/src/views/Company/ForecastReport.js b/src/views/Company/components/ForecastReport/index.js similarity index 99% rename from src/views/Company/ForecastReport.js rename to src/views/Company/components/ForecastReport/index.js index 5591145c..f42955e2 100644 --- a/src/views/Company/ForecastReport.js +++ b/src/views/Company/components/ForecastReport/index.js @@ -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'); diff --git a/src/views/Company/MarketDataView.js b/src/views/Company/components/MarketDataView/index.js similarity index 99% rename from src/views/Company/MarketDataView.js rename to src/views/Company/components/MarketDataView/index.js index f96acb16..783ef235 100644 --- a/src/views/Company/MarketDataView.js +++ b/src/views/Company/components/MarketDataView/index.js @@ -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([]); diff --git a/src/views/Company/index.js b/src/views/Company/index.js index cf5b3723..f8c8c449 100644 --- a/src/views/Company/index.js +++ b/src/views/Company/index.js @@ -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 ( - {/* 页面标题和股票搜索 */} - - - - - 个股详情 - - 查看股票实时行情、财务数据和盈利预测 - - - - - - - - - setInputCode(e.target.value)} - onKeyPress={handleKeyPress} - borderRadius="md" - _focus={{ - borderColor: 'blue.500', - boxShadow: '0 0 0 1px #3182ce' - }} - /> - - - - : } - onClick={toggleColorMode} - variant="outline" - colorScheme={colorMode === 'light' ? 'blue' : 'yellow'} - size="lg" - aria-label="Toggle color mode" - /> - - - - {/* 当前股票信息 */} - - - 股票代码: {stockCode} - - - 更新时间: {new Date().toLocaleString()} - - - - - - {/* 数据展示区域 */} - - - { - const tabNames = ['公司概览', '股票行情', '财务全景', '盈利预测']; - // 🎯 追踪 Tab 切换 - trackTabChanged(index, tabNames[index], currentTabIndex); - setCurrentTabIndex(index); - }} - > - - - - - 公司概览 - - - - - - 股票行情 - - - - - - 财务全景 - - - - - - 盈利预测 - - - - - - - - - - - - - - - - - - - - - - - + {/* 页面头部:标题、搜索、自选股按钮 */} + + + {/* Tab 切换区域:概览、行情、财务、预测 */} + ); }; export default CompanyIndex; - -