diff --git a/src/views/Company/components/CompanyHeader/SearchBar.js b/src/views/Company/components/CompanyHeader/SearchBar.js
new file mode 100644
index 00000000..ed556ad1
--- /dev/null
+++ b/src/views/Company/components/CompanyHeader/SearchBar.js
@@ -0,0 +1,59 @@
+// src/views/Company/components/CompanyHeader/SearchBar.js
+// 股票搜索栏组件
+
+import React from 'react';
+import {
+ HStack,
+ Input,
+ Button,
+ InputGroup,
+ InputLeftElement,
+} from '@chakra-ui/react';
+import { SearchIcon } from '@chakra-ui/icons';
+
+/**
+ * 股票搜索栏组件
+ *
+ * @param {Object} props
+ * @param {string} props.inputCode - 输入框当前值
+ * @param {Function} props.onInputChange - 输入变化回调
+ * @param {Function} props.onSearch - 搜索按钮点击回调
+ * @param {Function} props.onKeyPress - 键盘事件回调
+ */
+const SearchBar = ({
+ inputCode,
+ onInputChange,
+ onSearch,
+ onKeyPress,
+}) => {
+ return (
+
+
+
+
+
+ onInputChange(e.target.value)}
+ onKeyPress={onKeyPress}
+ borderRadius="md"
+ _focus={{
+ borderColor: 'blue.500',
+ boxShadow: '0 0 0 1px #3182ce',
+ }}
+ />
+
+ }
+ >
+ 查询
+
+
+ );
+};
+
+export default SearchBar;
diff --git a/src/views/Company/components/CompanyHeader/WatchlistButton.js b/src/views/Company/components/CompanyHeader/WatchlistButton.js
new file mode 100644
index 00000000..2b788ab8
--- /dev/null
+++ b/src/views/Company/components/CompanyHeader/WatchlistButton.js
@@ -0,0 +1,35 @@
+// src/views/Company/components/CompanyHeader/WatchlistButton.js
+// 自选股按钮组件
+
+import React from 'react';
+import { Button } from '@chakra-ui/react';
+import { StarIcon } from '@chakra-ui/icons';
+
+/**
+ * 自选股按钮组件
+ *
+ * @param {Object} props
+ * @param {boolean} props.isInWatchlist - 是否已在自选股中
+ * @param {boolean} props.isLoading - 是否正在加载
+ * @param {Function} props.onClick - 点击回调
+ */
+const WatchlistButton = ({
+ isInWatchlist,
+ isLoading,
+ onClick,
+}) => {
+ return (
+ }
+ isLoading={isLoading}
+ >
+ {isInWatchlist ? '已关注' : '关注'}
+
+ );
+};
+
+export default WatchlistButton;
diff --git a/src/views/Company/components/CompanyHeader/index.js b/src/views/Company/components/CompanyHeader/index.js
new file mode 100644
index 00000000..e46c7e30
--- /dev/null
+++ b/src/views/Company/components/CompanyHeader/index.js
@@ -0,0 +1,94 @@
+// src/views/Company/components/CompanyHeader/index.js
+// 公司详情页面头部区域组件
+
+import React from 'react';
+import {
+ Card,
+ CardBody,
+ HStack,
+ VStack,
+ Heading,
+ Text,
+ Badge,
+} from '@chakra-ui/react';
+
+import SearchBar from './SearchBar';
+import WatchlistButton from './WatchlistButton';
+
+/**
+ * 公司详情页面头部区域组件
+ *
+ * 包含:
+ * - 页面标题和描述
+ * - 股票搜索栏
+ * - 自选股按钮
+ * - 当前股票代码显示
+ *
+ * @param {Object} props
+ * @param {string} props.stockCode - 当前股票代码
+ * @param {string} props.inputCode - 搜索输入框值
+ * @param {Function} props.onInputChange - 输入变化回调
+ * @param {Function} props.onSearch - 搜索回调
+ * @param {Function} props.onKeyPress - 键盘事件回调
+ * @param {boolean} props.isInWatchlist - 是否在自选股中
+ * @param {boolean} props.isWatchlistLoading - 自选股操作加载中
+ * @param {Function} props.onWatchlistToggle - 自选股切换回调
+ * @param {string} props.bgColor - 背景颜色
+ */
+const CompanyHeader = ({
+ stockCode,
+ inputCode,
+ onInputChange,
+ onSearch,
+ onKeyPress,
+ isInWatchlist,
+ isWatchlistLoading,
+ onWatchlistToggle,
+ bgColor,
+}) => {
+ return (
+
+
+
+ {/* 标题区域 */}
+
+ 个股详情
+
+ 查看股票实时行情、财务数据和盈利预测
+
+
+
+ {/* 操作区域 */}
+
+ {/* 搜索栏 */}
+
+
+ {/* 自选股按钮 */}
+
+
+
+
+ {/* 当前股票信息 */}
+
+
+ 股票代码: {stockCode}
+
+
+ 更新时间: {new Date().toLocaleString()}
+
+
+
+
+ );
+};
+
+export default CompanyHeader;
diff --git a/src/views/Company/hooks/useCompanyWatchlist.js b/src/views/Company/hooks/useCompanyWatchlist.js
new file mode 100644
index 00000000..56019470
--- /dev/null
+++ b/src/views/Company/hooks/useCompanyWatchlist.js
@@ -0,0 +1,166 @@
+// src/views/Company/hooks/useCompanyWatchlist.js
+// 自选股管理 Hook - Company 页面专用,复用 Redux stockSlice
+
+import { useEffect, useCallback, useMemo, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useToast } from '@chakra-ui/react';
+import { useAuth } from '@contexts/AuthContext';
+import { logger } from '@utils/logger';
+import {
+ loadWatchlist,
+ toggleWatchlist,
+ optimisticAddWatchlist,
+ optimisticRemoveWatchlist
+} from '@store/slices/stockSlice';
+import { TOAST_MESSAGES } from '../constants';
+
+/**
+ * Company 页面自选股管理 Hook
+ *
+ * 功能:
+ * - 检查当前股票是否在自选股中
+ * - 提供添加/移除自选股功能
+ * - 与 Redux stockSlice 同步
+ *
+ * @param {Object} options - 配置选项
+ * @param {string} options.stockCode - 当前股票代码
+ * @param {Object} [options.tracking] - 追踪回调
+ * @param {Function} [options.tracking.onAdd] - 添加自选时的追踪回调
+ * @param {Function} [options.tracking.onRemove] - 移除自选时的追踪回调
+ * @returns {Object} 自选股状态和操作方法
+ */
+export const useCompanyWatchlist = ({ stockCode, tracking = {} } = {}) => {
+ const dispatch = useDispatch();
+ const toast = useToast();
+ const { isAuthenticated } = useAuth();
+
+ // 从 Redux 获取自选股列表
+ const watchlist = useSelector((state) => state.stock.watchlist);
+ const watchlistLoading = useSelector((state) => state.stock.loading.watchlist);
+
+ // 追踪是否已初始化(防止无限循环)
+ const hasInitializedRef = useRef(false);
+
+ /**
+ * 派生状态:判断当前股票是否在自选股中
+ * 使用 useMemo 避免重复计算
+ */
+ const isInWatchlist = useMemo(() => {
+ if (!stockCode || !Array.isArray(watchlist)) {
+ return false;
+ }
+
+ // 标准化股票代码(提取6位数字)
+ const normalize = (code) => String(code || '').match(/(\d{6})/)?.[1] || '';
+ const targetCode = normalize(stockCode);
+
+ return watchlist.some((item) => normalize(item.stock_code) === targetCode);
+ }, [watchlist, stockCode]);
+
+ /**
+ * 初始化:加载自选股列表
+ * 使用 hasInitializedRef 防止无限循环(用户可能确实没有自选股)
+ */
+ useEffect(() => {
+ if (!hasInitializedRef.current && isAuthenticated && !watchlistLoading) {
+ hasInitializedRef.current = true;
+ dispatch(loadWatchlist());
+ }
+ }, [isAuthenticated, watchlistLoading, dispatch]);
+
+ /**
+ * 切换自选股状态(乐观更新模式)
+ * 1. 立即更新 UI(无 loading)
+ * 2. 后台静默请求 API
+ * 3. 失败时回滚并提示
+ */
+ const toggle = useCallback(async () => {
+ // 参数校验
+ if (!stockCode) {
+ logger.warn('useCompanyWatchlist', 'toggle', '无效的股票代码', { stockCode });
+ toast(TOAST_MESSAGES.INVALID_CODE);
+ return;
+ }
+
+ // 权限校验
+ if (!isAuthenticated) {
+ logger.warn('useCompanyWatchlist', 'toggle', '用户未登录', { stockCode });
+ toast(TOAST_MESSAGES.LOGIN_REQUIRED);
+ return;
+ }
+
+ // 标准化股票代码用于匹配
+ const normalize = (code) => String(code || '').match(/(\d{6})/)?.[1] || '';
+ const targetCode = normalize(stockCode);
+
+ // 从 watchlist 中找到原始 stock_code(保持与后端数据结构一致)
+ const matchedItem = watchlist.find(
+ item => normalize(item.stock_code) === targetCode
+ );
+ // 移除时使用原始 stock_code,添加时使用传入的 stockCode
+ const codeForApi = isInWatchlist ? (matchedItem?.stock_code || stockCode) : stockCode;
+
+ // 保存当前状态用于回滚
+ const wasInWatchlist = isInWatchlist;
+
+ logger.debug('useCompanyWatchlist', '切换自选股(乐观更新)', {
+ stockCode,
+ codeForApi,
+ wasInWatchlist,
+ action: wasInWatchlist ? 'remove' : 'add',
+ });
+
+ // 1. 乐观更新:立即更新 UI(不显示 loading)
+ if (wasInWatchlist) {
+ dispatch(optimisticRemoveWatchlist({ stockCode: codeForApi }));
+ } else {
+ dispatch(optimisticAddWatchlist({ stockCode: codeForApi, stockName: matchedItem?.stock_name || '' }));
+ }
+
+ try {
+ // 2. 后台静默请求 API
+ await dispatch(
+ toggleWatchlist({
+ stockCode: codeForApi,
+ stockName: matchedItem?.stock_name || '',
+ isInWatchlist: wasInWatchlist,
+ })
+ ).unwrap();
+
+ // 3. 成功:触发追踪回调(不显示 toast,状态已更新)
+ if (wasInWatchlist) {
+ tracking.onRemove?.(stockCode);
+ } else {
+ tracking.onAdd?.(stockCode);
+ }
+ } catch (error) {
+ // 4. 失败:回滚状态 + 显示错误提示
+ logger.error('useCompanyWatchlist', 'toggle', error, {
+ stockCode,
+ wasInWatchlist,
+ });
+
+ // 回滚操作
+ if (wasInWatchlist) {
+ // 之前在自选中,乐观删除了,现在要恢复
+ dispatch(optimisticAddWatchlist({ stockCode: codeForApi, stockName: matchedItem?.stock_name || '' }));
+ } else {
+ // 之前不在自选中,乐观添加了,现在要移除
+ dispatch(optimisticRemoveWatchlist({ stockCode: codeForApi }));
+ }
+
+ toast(TOAST_MESSAGES.WATCHLIST_ERROR);
+ }
+ }, [stockCode, isAuthenticated, isInWatchlist, watchlist, dispatch, toast, tracking]);
+
+ return {
+ // 状态
+ isInWatchlist, // 是否在自选股中
+ isLoading: watchlistLoading, // 仅初始加载时显示 loading(乐观更新模式)
+
+ // 操作方法
+ toggle, // 切换自选状态
+ };
+};
+
+export default useCompanyWatchlist;