You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

A股智投分析平台 - 前端实现文档

一、组件清单

1.1 公共组件

组件名 文件路径 功能描述 复杂度
Navbar components/Navbar.tsx 导航栏,含搜索功能
CandlestickChart components/CandlestickChart.tsx K线蜡烛图+均线+成交量
StockDetailModal components/StockDetailModal.tsx 个股详情弹窗
SectorDetailModal components/SectorDetailModal.tsx 版块详情弹窗
Footer components/Footer.tsx 页脚

1.2 页面区块组件

组件名 文件路径 功能描述 复杂度
MarketOverview sections/MarketOverview.tsx 市场概览
MomentumSectors sections/MomentumSectors.tsx 动量版块分析
HighLowStocks sections/HighLowStocks.tsx 新高新低个股
PriceDistribution sections/PriceDistribution.tsx 涨跌幅分布
MomentumRecommendation sections/MomentumRecommendation.tsx 动量股推荐

二、核心组件详解

2.1 CandlestickChart 组件

功能: K线蜡烛图支持均线和成交量

Props

interface CandlestickChartProps {
    data: KLineData[];           // K线数据
    height?: number;             // 图表高度默认400
    showVolume?: boolean;        // 是否显示成交量默认true
    showMaSettings?: boolean;    // 是否显示均线设置默认true
}

实现要点

  • 使用 Recharts ComposedChart 组合图表
  • 自定义蜡烛图形状(红涨绿跌)
  • 支持5条均线MA5/MA10/MA20/MA30/MA60
  • 成交量附图颜色与K线对应
  • 点击均线标签切换显示/隐藏

代码片段

// 均线配置
const defaultMaPeriods: MaPeriod[] = [
    { key: 'ma5', label: 'MA5', days: 5, color: '#ff9f43', visible: true },
    { key: 'ma10', label: 'MA10', days: 10, color: '#3498db', visible: true },
    { key: 'ma20', label: 'MA20', days: 20, color: '#9b59b6', visible: true },
    { key: 'ma30', label: 'MA30', days: 30, color: '#e74c3c', visible: false },
    { key: 'ma60', label: 'MA60', days: 60, color: '#2ecc71', visible: false },
];

// 渲染蜡烛图
const renderCandle = (props, maxPrice, minPrice, pricePadding) => {
    const { x, y, width, height, payload } = props;
    const { open, close } = payload;
    const isUp = close >= open;
    const color = isUp ? '#ff3b30' : '#00c853';
    
    // 计算影线坐标
    // 计算实体坐标
    // 返回SVG元素
};

2.2 StockDetailModal 组件

功能: 个股详情弹窗

Props

interface StockDetailModalProps {
    stockCode: string | null;    // 股票代码
    isOpen: boolean;             // 是否打开
    onClose: () => void;         // 关闭回调
}

实现要点

  • 使用 Framer Motion 实现动画
  • 获取个股详情和K线数据
  • 显示基本信息、K线图、技术指标、基本面
  • 支持日线/周线/月线切换

代码片段

const [stock, setStock] = useState<StockDetail | null>(null);
const [klineData, setKlineData] = useState<KLineData[]>([]);
const [timeRange, setTimeRange] = useState<'day' | 'week' | 'month'>('day');

useEffect(() => {
    if (stockCode && isOpen) {
        setStock(stockDataService.getStockDetail(stockCode));
        setKlineData(stockDataService.getKLineData(stockCode, 60));
    }
}, [stockCode, isOpen]);

2.3 SectorDetailModal 组件

功能: 版块详情弹窗

Props

interface SectorDetailModalProps {
    sector: Sector | null;       // 版块数据
    isOpen: boolean;             // 是否打开
    onClose: () => void;         // 关闭回调
    onStockClick?: (code: string) => void;  // 股票点击回调
}

实现要点

  • 三个标签页历史排名、动量个股、K线走势
  • 历史排名使用组合图表(排名线+动量分柱状图)
  • 动量个股列表支持点击打开个股详情
  • K线使用 CandlestickChart 组件

2.4 Navbar 组件

功能: 导航栏,含搜索功能

Props

interface NavbarProps {
    onSectorClick?: (sector: Sector) => void;    // 版块点击回调
    onStockClick?: (code: string) => void;       // 个股点击回调
}

实现要点

  • 滚动时添加背景和边框
  • 搜索框展开/收起动画
  • 实时搜索,分类展示结果
  • 点击结果打开对应详情

代码片段

const [searchOpen, setSearchOpen] = useState(false);
const [searchKeyword, setSearchKeyword] = useState('');
const [searchResults, setSearchResults] = useState({ sectors: [], stocks: [] });

// 搜索逻辑
useEffect(() => {
    if (searchKeyword.trim().length >= 1) {
        const sectors = stockDataService.searchSectors(searchKeyword);
        const stocks = stockDataService.searchStocks(searchKeyword);
        setSearchResults({ sectors, stocks });
    }
}, [searchKeyword]);

三、数据服务

3.1 StockDataService

文件: services/stockData.ts

主要方法

方法名 功能 返回值
getMarketIndices() 获取市场指数 MarketIndex[]
getUpDownStats() 获取涨跌家数 {up, down, flat}
getSectorsWithMomentum() 获取版块列表(带动量) Sector[]
getSectorRankHistory(name) 获取版块历史排名 SectorMomentumHistory[]
getSectorStocks(name) 获取版块内股票 Stock[]
getSectorMomentumStocks(name) 获取版块内动量股票 MomentumStock[]
getSectorKLineData(name, days) 获取版块K线 KLineData[]
getNewHighStocks() 获取创新高股票 HighLowStock[]
getNewLowStocks() 获取创新低股票 HighLowStock[]
getPriceDistribution() 获取涨跌幅分布 PriceDistribution[]
getMomentumStocks() 获取动量股推荐 MomentumStock[]
getStockDetail(code) 获取个股详情 StockDetail
getKLineData(code, days) 获取个股K线 KLineData[]
searchSectors(keyword) 搜索版块 Sector[]
searchStocks(keyword) 搜索股票 Stock[]

3.2 均线计算

代码片段

private calculateMA(data: KLineData[]): KLineData[] {
    const periods = [5, 10, 20, 30, 60];
    
    return data.map((item, index) => {
        const ma: Record<string, number> = {};
        
        for (const period of periods) {
            if (index >= period - 1) {
                const sum = data
                    .slice(index - period + 1, index + 1)
                    .reduce((acc, d) => acc + d.close, 0);
                ma[`ma${period}`] = this.formatNumber(sum / period);
            }
        }
        
        return { ...item, ...ma };
    });
}

四、类型定义

4.1 核心类型

文件: types/index.ts

// 股票基础信息
export interface Stock {
    code: string;           // 股票代码
    name: string;           // 股票名称
    price: number;          // 当前价格
    change: number;         // 涨跌额
    changePercent: number;  // 涨跌幅
    volume: number;         // 成交量
    turnover: number;       // 成交额
    marketCap?: number;     // 总市值
    pe?: number;            // 市盈率
    pb?: number;            // 市净率
    industry?: string;      // 所属行业
}

// 版块信息
export interface Sector {
    name: string;           // 版块名称
    code: string;           // 版块代码
    change: number;         // 涨跌额
    changePercent: number;  // 涨跌幅
    volume: number;         // 成交量
    turnover: number;       // 成交额
    leadingStock?: string;  // 领涨股
    momentumScore?: number; // 动量分数
    rank?: number;          // 当前排名
    previousRank?: number;  // 昨日排名
    rankChange?: number;    // 排名变化
}

// K线数据
export interface KLineData {
    date: string;           // 日期
    open: number;           // 开盘价
    high: number;           // 最高价
    low: number;            // 最低价
    close: number;          // 收盘价
    volume: number;         // 成交量
    ma5?: number;           // 5日均线
    ma10?: number;          // 10日均线
    ma20?: number;          // 20日均线
    ma30?: number;          // 30日均线
    ma60?: number;          // 60日均线
}

// 均线周期配置
export interface MaPeriod {
    key: string;            // 标识
    label: string;          // 显示名称
    days: number;           // 周期天数
    color: string;          // 颜色
    visible: boolean;       // 是否显示
}

五、动画实现

5.1 页面加载动画

const containerVariants = {
    hidden: { opacity: 0 },
    visible: {
        opacity: 1,
        transition: {
            staggerChildren: 0.1,
            delayChildren: 0.2
        }
    }
};

const itemVariants = {
    hidden: { opacity: 0, y: 30 },
    visible: {
        opacity: 1,
        y: 0,
        transition: {
            duration: 0.6,
            ease: [0.165, 0.84, 0.44, 1] as const
        }
    }
};

5.2 数字动画

function AnimatedNumber({ value, decimals = 2 }: { value: number; decimals?: number }) {
    const [displayValue, setDisplayValue] = useState(0);
    const prevValue = useRef(value);
    
    useEffect(() => {
        const start = prevValue.current;
        const end = value;
        const duration = 800;
        const startTime = performance.now();
        
        const animate = (currentTime: number) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);
            const easeOut = 1 - Math.pow(1 - progress, 4);
            const current = start + (end - start) * easeOut;
            
            setDisplayValue(current);
            
            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                prevValue.current = value;
            }
        };
        
        requestAnimationFrame(animate);
    }, [value]);
    
    return <span className="number-font">{displayValue.toFixed(decimals)}</span>;
}

5.3 弹窗动画

<motion.div
    initial={{ opacity: 0, scale: 0.9, y: 20 }}
    animate={{ opacity: 1, scale: 1, y: 0 }}
    exit={{ opacity: 0, scale: 0.9, y: 20 }}
    transition={{ duration: 0.3, ease: [0.165, 0.84, 0.44, 1] }}
>
    {/* 弹窗内容 */}
</motion.div>

六、样式规范

6.1 颜色系统

/* 主色调 */
--background: #0a0a0a;      /* 背景色 */
--card: #1a1a1a;            /* 卡片背景 */
--border: #2a2a2a;          /* 边框色 */
--accent: #ff6b35;          /* 强调色(橙色) */

/* 文字色 */
--text-primary: #ffffff;    /* 主文字 */
--text-secondary: #b0b0b0;  /* 次要文字 */

/* 功能色 */
--up: #ff3b30;              /* 上涨(红) */
--down: #00c853;            /* 下跌(绿) */

/* 均线色 */
--ma5: #ff9f43;             /* MA5 - 橙色 */
--ma10: #3498db;            /* MA10 - 蓝色 */
--ma20: #9b59b6;            /* MA20 - 紫色 */
--ma30: #e74c3c;            /* MA30 - 红色 */
--ma60: #2ecc71;            /* MA60 - 绿色 */

6.2 字体规范

/* 字体家族 */
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

/* 数字字体 */
.number-font {
    font-family: 'JetBrains Mono', monospace;
    font-variant-numeric: tabular-nums;
}

6.3 间距规范

/* 区块间距 */
--section-gap: 3rem;        /* 48px */
--card-gap: 1rem;           /* 16px */
--card-padding: 1.5rem;     /* 24px */

/* 圆角 */
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;

七、性能优化

7.1 已实现的优化

优化项 实现方式
组件懒加载 使用动态导入
数据缓存 useMemo 缓存计算结果
动画优化 使用 transform 和 opacity
虚拟列表 大量数据时使用

7.2 待优化项

  • 图片懒加载
  • Service Worker 缓存
  • 代码分割优化
  • Tree Shaking

八、测试策略

8.1 单元测试

// 示例: CandlestickChart 测试
describe('CandlestickChart', () => {
    it('should render candlestick chart', () => {
        const data = [
            { date: '2024-01-15', open: 50, high: 55, low: 48, close: 52, volume: 1000000 }
        ];
        render(<CandlestickChart data={data} />);
        expect(screen.getByRole('img')).toBeInTheDocument();
    });
});

8.2 E2E测试

// 示例: 搜索功能测试
describe('Search', () => {
    it('should search and display results', () => {
        cy.visit('/');
        cy.get('[data-testid="search-button"]').click();
        cy.get('[data-testid="search-input"]').type('茅台');
        cy.get('[data-testid="search-result"]').should('contain', '贵州茅台');
    });
});

九、待实现功能

9.1 前端待实现

  • 用户登录/注册页面
  • 自选股管理页面
  • 预警设置页面
  • 主题切换(深色/浅色)
  • 多语言支持

9.2 与后端对接

  • 接入真实API
  • WebSocket实时数据
  • 用户认证
  • 数据持久化