From 390f6024f4083ff6f9ecf11014c9dd131f976e73 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Tue, 3 Feb 2026 14:05:52 +0800 Subject: [PATCH] update pay promo --- src/components/AddStockModal/index.js | 221 ++++++++++++++++++ src/components/Navbars/SearchBar/SearchBar.js | 25 +- .../components/StockQuoteCard/index.tsx | 64 ++++- .../WatchSidebar/components/WatchlistPanel.js | 17 +- 4 files changed, 303 insertions(+), 24 deletions(-) create mode 100644 src/components/AddStockModal/index.js diff --git a/src/components/AddStockModal/index.js b/src/components/AddStockModal/index.js new file mode 100644 index 00000000..2af1e538 --- /dev/null +++ b/src/components/AddStockModal/index.js @@ -0,0 +1,221 @@ +/** + * AddStockModal - 添加自选股弹窗 + * + * 支持搜索股票并添加到自选股 + */ + +import React, { useCallback, useState } from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalCloseButton, + Input, + InputGroup, + InputLeftElement, + InputRightElement, + VStack, + HStack, + Text, + Box, + Spinner, + Icon, + IconButton, + useToast, +} from '@chakra-ui/react'; +import { Search, X, Plus, Check } from 'lucide-react'; +import { useStockSearch } from '@hooks/useStockSearch'; +import { useWatchlist } from '@hooks/useWatchlist'; + +const AddStockModal = ({ isOpen, onClose }) => { + const toast = useToast(); + const [addingCode, setAddingCode] = useState(null); + + // 搜索相关 + const { + searchQuery, + searchResults, + isSearching, + handleSearch, + clearSearch, + } = useStockSearch({ limit: 15, debounceMs: 300 }); + + // 自选股相关 + const { handleAddToWatchlist, isInWatchlist } = useWatchlist(); + + // 添加股票 + const handleAdd = useCallback(async (stock) => { + if (addingCode) return; + + setAddingCode(stock.stock_code); + try { + const success = await handleAddToWatchlist(stock.stock_code, stock.stock_name); + if (success) { + // 添加成功后不关闭弹窗,用户可以继续添加 + } + } finally { + setAddingCode(null); + } + }, [addingCode, handleAddToWatchlist]); + + // 关闭弹窗时清空搜索 + const handleClose = useCallback(() => { + clearSearch(); + onClose(); + }, [clearSearch, onClose]); + + return ( + + + + + + 添加自选股 + + + + + + + {/* 搜索框 */} + + + + + handleSearch(e.target.value)} + bg="rgba(255, 255, 255, 0.05)" + border="1px solid" + borderColor="rgba(255, 255, 255, 0.1)" + color="white" + _placeholder={{ color: 'gray.500' }} + _hover={{ borderColor: 'rgba(212, 175, 55, 0.5)' }} + _focus={{ + borderColor: '#D4AF37', + boxShadow: '0 0 0 1px rgba(212, 175, 55, 0.3)', + }} + autoFocus + /> + {(searchQuery || isSearching) && ( + + {isSearching ? ( + + ) : ( + } + onClick={clearSearch} + aria-label="清除搜索" + color="gray.400" + _hover={{ color: 'white', bg: 'transparent' }} + /> + )} + + )} + + + {/* 搜索结果 */} + + {searchResults.length > 0 ? ( + + {searchResults.map((stock) => { + const inWatchlist = isInWatchlist(stock.stock_code); + const isAdding = addingCode === stock.stock_code; + + return ( + + + + {stock.stock_name} + + + + {stock.stock_code} + + {stock.pinyin_abbr && ( + + ({stock.pinyin_abbr.toUpperCase()}) + + )} + + + + {inWatchlist ? ( + + + 已添加 + + ) : ( + : } + onClick={() => handleAdd(stock)} + isDisabled={isAdding} + _hover={{ bg: 'rgba(212, 175, 55, 0.15)' }} + aria-label="添加到自选股" + /> + )} + + ); + })} + + ) : searchQuery ? ( + + + {isSearching ? '搜索中...' : '未找到相关股票'} + + + ) : ( + + + + 输入股票代码或名称开始搜索 + + + )} + + + + + + ); +}; + +export default AddStockModal; diff --git a/src/components/Navbars/SearchBar/SearchBar.js b/src/components/Navbars/SearchBar/SearchBar.js index dd3e59a3..e0576769 100755 --- a/src/components/Navbars/SearchBar/SearchBar.js +++ b/src/components/Navbars/SearchBar/SearchBar.js @@ -28,11 +28,11 @@ export function SearchBar(props) { const navigate = useNavigate(); const containerRef = useRef(null); - // 颜色配置 - 固定使用深色主题 - const searchIconColor = "gray.400"; - const inputBg = "whiteAlpha.100"; + // 颜色配置 - 固定使用深色主题(增强可见性) + const searchIconColor = "#D4AF37"; + const inputBg = "rgba(26, 32, 44, 0.9)"; const dropdownBg = "#1a1a2e"; - const borderColor = "rgba(212, 175, 55, 0.3)"; + const borderColor = "rgba(212, 175, 55, 0.5)"; const hoverBg = "whiteAlpha.100"; const textColor = "white"; const subTextColor = "whiteAlpha.600"; @@ -78,22 +78,29 @@ export function SearchBar(props) { return ( - + - + handleSearch(e.target.value)} onKeyDown={handleKeyDown} onFocus={() => searchQuery && searchResults.length > 0 && setShowResults(true)} + border="1px solid" borderColor={borderColor} - _hover={{ borderColor: accentColor }} - _focus={{ borderColor: accentColor, boxShadow: `0 0 0 1px ${accentColor}` }} + _placeholder={{ color: "gray.400" }} + _hover={{ borderColor: accentColor, bg: "rgba(26, 32, 44, 1)" }} + _focus={{ + borderColor: accentColor, + boxShadow: `0 0 0 1px ${accentColor}`, + bg: "rgba(26, 32, 44, 1)" + }} /> {(searchQuery || isSearching) && ( diff --git a/src/views/Company/components/StockQuoteCard/index.tsx b/src/views/Company/components/StockQuoteCard/index.tsx index f07b2514..a7d1c5cf 100644 --- a/src/views/Company/components/StockQuoteCard/index.tsx +++ b/src/views/Company/components/StockQuoteCard/index.tsx @@ -8,13 +8,13 @@ * * 组件结构: * - StockHeader:股票名称、代码、行业标签、操作按钮 - * - PriceDisplay:当前价格、涨跌幅 Badge - * - SecondaryQuote:今开、昨收、最高、最低 + * - PriceDisplay:当前价格、涨跌幅 Badge(实时更新) + * - SecondaryQuote:今开、昨收、最高、最低(实时更新) * - GlassSection + MetricRow:估值指标、市值股本 * - MainForceInfo:主力动态 */ -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { Box, Flex, VStack, useDisclosure, useToast } from '@chakra-ui/react'; import { CardGlow } from '@components/FUI'; @@ -35,6 +35,7 @@ import { glassCardStyle } from './components/theme'; // Hooks import { useStockQuoteData, useStockCompare } from './hooks'; +import { useRealtimeQuote } from '@views/StockOverview/components/FlexScreen/hooks'; import type { StockQuoteCardProps } from './types'; const StockQuoteCard: React.FC = ({ @@ -52,6 +53,45 @@ const StockQuoteCard: React.FC = ({ clearCompare, } = useStockCompare(stockCode); + // 实时行情订阅 + const subscribedCodes = useMemo(() => { + if (!stockCode) return []; + // 标准化股票代码,添加交易所后缀 + const baseCode = stockCode.split('.')[0]; + // 根据代码判断交易所 + const isShanghai = baseCode.startsWith('6') || baseCode.startsWith('5'); + return [isShanghai ? `${baseCode}.SH` : `${baseCode}.SZ`]; + }, [stockCode]); + + const { quotes } = useRealtimeQuote(subscribedCodes); + + // 获取当前股票的实时行情数据 + const realtimeQuote = useMemo(() => { + if (!stockCode || !quotes) return null; + const baseCode = stockCode.split('.')[0]; + return quotes[`${baseCode}.SH`] || quotes[`${baseCode}.SZ`] || null; + }, [quotes, stockCode]); + + // 合并实时数据和静态数据 + const displayData = useMemo(() => { + if (!quoteData) return null; + + // 如果有实时数据,用实时数据覆盖价格相关字段 + if (realtimeQuote && realtimeQuote.price > 0) { + return { + ...quoteData, + currentPrice: realtimeQuote.price, + changePercent: realtimeQuote.changePct, + todayOpen: realtimeQuote.open || quoteData.todayOpen, + todayHigh: realtimeQuote.high || quoteData.todayHigh, + todayLow: realtimeQuote.low || quoteData.todayLow, + yesterdayClose: realtimeQuote.prevClose || quoteData.yesterdayClose, + }; + } + + return quoteData; + }, [quoteData, realtimeQuote]); + const { isOpen: isCompareModalOpen, onOpen: openCompareModal, onClose: closeCompareModal } = useDisclosure(); const handleCompare = (compareCode: string) => { @@ -93,7 +133,7 @@ const StockQuoteCard: React.FC = ({ }, [stockCode, toast]); // 加载中 - if (isLoading || !quoteData) { + if (isLoading || !displayData) { return ; } @@ -119,18 +159,18 @@ const StockQuoteCard: React.FC = ({ onCompare={handleCompare} /> - {/* ========== 价格区域 ========== */} + {/* ========== 价格区域(实时更新)========== */} - {/* ========== 次要行情 ========== */} + {/* ========== 次要行情(实时更新)========== */} {/* ========== 数据区块(三列布局)========== */} diff --git a/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js b/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js index a9a2a0e8..dec673f1 100644 --- a/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js +++ b/src/views/Profile/components/WatchSidebar/components/WatchlistPanel.js @@ -1,8 +1,9 @@ // 关注股票面板 - 紧凑版 import React, { useState } from 'react'; -import { Box, Text, VStack, HStack, Icon, Badge } from '@chakra-ui/react'; +import { Box, Text, VStack, HStack, Icon, Badge, useDisclosure } from '@chakra-ui/react'; import { BarChart2, Plus } from 'lucide-react'; import FavoriteButton from '@/components/FavoriteButton'; +import AddStockModal from '@/components/AddStockModal'; /** * 格式化涨跌幅 @@ -26,6 +27,7 @@ const WatchlistPanel = ({ hideTitle = false, }) => { const [removingCode, setRemovingCode] = useState(null); + const { isOpen: isAddModalOpen, onOpen: onAddModalOpen, onClose: onAddModalClose } = useDisclosure(); const handleUnwatch = async (stockCode) => { if (removingCode) return; @@ -36,6 +38,12 @@ const WatchlistPanel = ({ setRemovingCode(null); } }; + + // 点击加号按钮 - 打开添加自选股弹窗 + const handleAddClick = () => { + onAddModalOpen(); + }; + return ( {/* 标题 - 可隐藏 */} @@ -56,7 +64,7 @@ const WatchlistPanel = ({ color="rgba(255, 255, 255, 0.5)" cursor="pointer" _hover={{ color: 'rgba(212, 175, 55, 0.9)' }} - onClick={onAddStock} + onClick={handleAddClick} /> )} @@ -67,7 +75,7 @@ const WatchlistPanel = ({ py={4} textAlign="center" cursor="pointer" - onClick={onAddStock} + onClick={handleAddClick} _hover={{ bg: 'rgba(255, 255, 255, 0.05)' }} borderRadius="md" > @@ -190,6 +198,9 @@ const WatchlistPanel = ({ )} + + {/* 添加自选股弹窗 */} + ); };