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 = ({
)}
+
+ {/* 添加自选股弹窗 */}
+
);
};