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.

483 lines
13 KiB

# 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**
```typescript
interface CandlestickChartProps {
data: KLineData[]; // K线数据
height?: number; // 图表高度默认400
showVolume?: boolean; // 是否显示成交量默认true
showMaSettings?: boolean; // 是否显示均线设置默认true
}
```
**实现要点**
- 使用 Recharts ComposedChart 组合图表
- 自定义蜡烛图形状(红涨绿跌)
- 支持5条均线MA5/MA10/MA20/MA30/MA60
- 成交量附图颜色与K线对应
- 点击均线标签切换显示/隐藏
**代码片段**
```typescript
// 均线配置
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**
```typescript
interface StockDetailModalProps {
stockCode: string | null; // 股票代码
isOpen: boolean; // 是否打开
onClose: () => void; // 关闭回调
}
```
**实现要点**
- 使用 Framer Motion 实现动画
- 获取个股详情和K线数据
- 显示基本信息、K线图、技术指标、基本面
- 支持日线/周线/月线切换
**代码片段**
```typescript
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**
```typescript
interface SectorDetailModalProps {
sector: Sector | null; // 版块数据
isOpen: boolean; // 是否打开
onClose: () => void; // 关闭回调
onStockClick?: (code: string) => void; // 股票点击回调
}
```
**实现要点**
- 三个标签页历史排名、动量个股、K线走势
- 历史排名使用组合图表(排名线+动量分柱状图)
- 动量个股列表支持点击打开个股详情
- K线使用 CandlestickChart 组件
### 2.4 Navbar 组件
**功能**: 导航栏,含搜索功能
**Props**
```typescript
interface NavbarProps {
onSectorClick?: (sector: Sector) => void; // 版块点击回调
onStockClick?: (code: string) => void; // 个股点击回调
}
```
**实现要点**
- 滚动时添加背景和边框
- 搜索框展开/收起动画
- 实时搜索,分类展示结果
- 点击结果打开对应详情
**代码片段**
```typescript
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 均线计算
**代码片段**
```typescript
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`
```typescript
// 股票基础信息
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 页面加载动画
```typescript
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 数字动画
```typescript
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 弹窗动画
```typescript
<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 颜色系统
```css
/* 主色调 */
--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 字体规范
```css
/* 字体家族 */
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* 数字字体 */
.number-font {
font-family: 'JetBrains Mono', monospace;
font-variant-numeric: tabular-nums;
}
```
### 6.3 间距规范
```css
/* 区块间距 */
--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 单元测试
```typescript
// 示例: 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测试
```typescript
// 示例: 搜索功能测试
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实时数据
- [ ] 用户认证
- [ ] 数据持久化