feat: 初始化代码;当前仅前端演示,详情页存在bug

master
Lxy 3 months ago
commit 83a136092a

24
.gitignore vendored

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,16 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

@ -0,0 +1,376 @@
# AI期货分析系统前端设计方案
## 1. 设计概述
### 1.1 设计目标
- 打造专业、直观的期货分析系统界面
- 提供流畅的用户交互体验
- 实现多维度数据可视化
- 确保全设备响应式适配
### 1.2 技术栈
- **前端框架**React + Ant Design
- **状态管理**Redux Toolkit
- **数据可视化**ECharts + D3.js
- **K线图表**TradingView Lightweight Charts
- **样式方案**Ant Design 默认主题 + 自定义样式
- **构建工具**Vite
### 1.3 设计风格
- **主色调**#1890ffAnt Design 蓝色)
- **辅助色**#52c41a上涨绿、#ff4d4f下跌红、#faad14中性黄
- **背景色**#f0f2f5浅色背景
- **文字色**#262626主文字、#8c8c8c次要文字
- **卡片样式**轻微阴影圆角4px
- **图标风格**Ant Design 图标库
## 2. 页面结构与布局
### 2.1 整体布局
- **顶部导航栏**:系统标题、用户信息、全局搜索、主题切换
- **左侧菜单**:功能模块导航,支持折叠
- **主内容区**:根据选择的功能模块显示相应内容
- **右侧信息栏**:市场热点、风险预警(可折叠)
### 2.2 响应式适配
- **桌面端**≥1200px完整三栏布局
- **平板端**768px-1199px左侧菜单折叠右侧信息栏可收起
- **移动端**<768px):顶部导航栏简化,左侧菜单转为抽屉式,右侧信息栏隐藏
## 3. 页面设计详情
### 3.1 Dashboard主页
#### 布局结构
- **页面头部**:欢迎信息、市场概览、快速操作按钮
- **品种概览区**:网格布局的品种卡片,支持分页和筛选
- **市场热点区**:热力图展示各品种表现
- **风险预警区**:重要风险提示列表
- **AI研判区**AI对市场的整体判断和预测
#### 交互设计
- **品种卡片**:点击进入详情页,悬停显示更多信息
- **筛选功能**:按品种类型、涨跌幅、胜率等筛选
- **排序功能**:支持多种排序方式
- **刷新机制**自动定时刷新默认30秒支持手动刷新
- **布局切换**:支持卡片网格和列表视图切换
#### 数据展示
- **品种卡片**显示品种名称、代码、当前价格、涨跌幅、胜率、ATR、ADX、多周期趋势判断
- **热力图**:颜色深浅表示涨跌幅大小
- **风险预警**:按严重程度排序,红色表示高风险
### 3.2 详情分析页
#### 布局结构
- **页面头部**:品种信息、代码、当前价格、涨跌幅
- **K线图表区**专业K线图支持多周期切换
- **技术指标区**:可切换显示不同技术指标
- **AI研判区**AI对该品种的详细分析和预测
- **交易建议区**:入场价、止损价、目标价建议
- **风险评估区**:风险等级、波动率分析
#### 交互设计
- **K线周期切换**5分钟、30分钟、1小时、1天、1周等
- **指标叠加**可在K线上叠加MA、MACD等指标
- **图表缩放**:鼠标滚轮缩放,拖拽平移
- **数据对比**:支持与其他品种或指数对比
- **历史回测**显示AI策略的历史表现
#### 数据展示
- **K线图**:专业蜡烛图,支持成交量柱状图
- **技术指标**MA、MACD、KDJ、RSI、布林带等
- **多周期趋势**5MIN、30MIN、1HOUR、1DAY的趋势判断
- **AI预测**:趋势预测图、胜率评估、预期收益
### 3.3 风控管理页
#### 布局结构
- **页面头部**:风控概览、风险等级
- **止损策略区**:止损设置表单,支持多种止损策略
- **仓位管理区**:资金分配、仓位计算器
- **换月预警区**:即将到期合约提醒
- **风险监控区**:实时风险指标监控
#### 交互设计
- **止损策略选择**:下拉选择不同止损策略,自动计算止损点位
- **仓位计算器**:输入资金、风险偏好,自动计算建议仓位
- **换月提醒**:点击查看详细换月策略
- **风险阈值设置**:可调整各项风险指标的预警阈值
#### 数据展示
- **止损点位**:动态计算的止损价格
- **仓位建议**:基于风险偏好的建议仓位比例
- **换月日历**:合约到期日倒计时
- **风险指标**:风险价值、最大回撤、夏普比率等
### 3.4 配置管理页
#### 布局结构
- **页面头部**:配置管理标题
- **数据源配置区**:数据源列表、优先级设置
- **AI模型配置区**:模型选择、参数调优
- **系统配置区**:分析周期、预警阈值等设置
- **用户偏好区**:界面偏好、通知设置
#### 交互设计
- **数据源切换**:开关控制数据源启用/禁用
- **模型参数调整**:滑块或输入框调整参数
- **配置保存**:实时保存或手动保存
- **配置重置**:恢复默认配置
#### 数据展示
- **数据源状态**:在线/离线状态,响应时间
- **模型性能**:不同模型的准确率、响应时间对比
- **系统参数**:当前配置值,默认值参考
## 4. 核心组件设计
### 4.1 导航组件
- **顶部导航栏**:系统标题、全局搜索、用户头像、消息通知、主题切换
- **左侧菜单**:折叠/展开功能,当前选中状态高亮,支持多级菜单
### 4.2 数据可视化组件
- **K线图表组件**:支持多周期切换,技术指标叠加,图表缩放
- **热力图组件**:多品种涨跌幅对比,颜色渐变表示强度
- **趋势分析组件**AI预测趋势线置信区间显示
- **技术指标组件**:可切换不同技术指标,支持参数调整
### 4.3 功能组件
- **品种卡片组件**:显示品种关键信息,支持点击进入详情
- **风险预警组件**:风险等级标识,详细信息展开
- **止损计算器组件**:根据策略计算止损点位
- **仓位计算器组件**:基于风险偏好计算建议仓位
- **数据源配置组件**:数据源管理,状态监控
- **AI模型配置组件**:模型选择,参数调优界面
### 4.4 通用组件
- **搜索组件**:支持模糊搜索,历史记录
- **筛选组件**:多条件组合筛选
- **分页组件**:数据分页显示,支持每页条数调整
- **加载组件**:数据加载动画,骨架屏
- **通知组件**:系统消息,预警通知
## 5. 交互流程设计
### 5.1 主要用户流程
#### 流程1查看市场概览
1. 用户登录系统 → 进入Dashboard主页
2. 浏览品种概览卡片 → 查看市场热点热力图
3. 查看风险预警 → 了解AI整体研判
4. 可通过筛选/排序找到感兴趣的品种
#### 流程2深入分析单个品种
1. 在Dashboard点击品种卡片 → 进入详情分析页
2. 查看K线图表 → 切换不同周期
3. 叠加技术指标 → 分析多周期趋势
4. 查看AI研判结果 → 参考交易建议
5. 评估风险等级 → 制定交易策略
#### 流程3设置风控参数
1. 从左侧菜单进入风控管理页
2. 设置止损策略 → 调整止损参数
3. 使用仓位计算器 → 确定资金分配
4. 查看换月预警 → 提前规划换月
5. 监控风险指标 → 确保风险可控
#### 流程4配置系统参数
1. 从左侧菜单进入配置管理页
2. 管理数据源 → 设置优先级
3. 选择AI模型 → 调整模型参数
4. 设置系统参数 → 调整预警阈值
5. 保存配置 → 应用新设置
### 5.2 微交互设计
- **按钮反馈**:点击时有轻微的缩放效果,悬停时有背景色变化
- **卡片悬停**:轻微上浮效果,阴影加深
- **数据更新**:数字变化时有平滑过渡动画
- **图表加载**:渐进式动画,提升视觉体验
- **通知提醒**:弹出动画,吸引用户注意
- **表单验证**:实时验证,错误提示动画
## 6. 响应式设计
### 6.1 桌面端≥1200px
- 完整三栏布局,左侧菜单展开
- 丰富的数据展示,多列布局
- 支持复杂的交互操作
### 6.2 平板端768px-1199px
- 左侧菜单默认折叠hover展开
- 右侧信息栏可收起,通过按钮展开
- 卡片布局改为2列
- 部分复杂图表简化显示
### 6.3 移动端(<768px
- 左侧菜单转为抽屉式,通过顶部按钮打开
- 右侧信息栏隐藏,重要信息整合到主内容区
- 卡片布局改为单列
- K线图表简化仅显示核心信息
- 表单元素适配触摸操作,增大点击区域
## 7. 性能优化
### 7.1 前端性能优化
- **代码分割**使用React.lazy和Suspense实现组件懒加载
- **状态管理**合理使用Redux避免不必要的重渲染
- **数据缓存**:缓存频繁访问的数据,减少重复请求
- **图表优化**:大数据量图表使用虚拟滚动,按需渲染
- **图片优化**使用适当尺寸的图片支持WebP格式
### 7.2 交互体验优化
- **骨架屏**:数据加载时显示骨架屏,提升感知性能
- **防抖节流**:对搜索、滚动等操作进行防抖节流处理
- **预加载**:预测用户可能访问的内容,提前加载
- **离线支持**:关键功能支持离线访问
- **错误处理**:友好的错误提示,避免白屏
## 8. 开发实现指南
### 8.1 项目结构
```
/src
/components # 通用组件
/layout # 布局组件
/charts # 图表组件
/forms # 表单组件
/common # 通用UI组件
/pages # 页面组件
/dashboard # 主页
/detail # 详情分析页
/risk-control # 风控管理页
/config # 配置管理页
/services # API服务
/store # Redux状态管理
/utils # 工具函数
/constants # 常量定义
/hooks # 自定义Hooks
App.jsx # 应用根组件
main.jsx # 应用入口
```
### 8.2 关键技术实现
- **K线图表**使用TradingView Lightweight Charts实现专业K线图
- **数据可视化**使用ECharts实现热力图、趋势分析等图表
- **响应式布局**使用Ant Design的Layout组件结合媒体查询
- **状态管理**使用Redux Toolkit管理全局状态
- **API调用**使用Axios实现API请求支持拦截器
- **模拟数据**在src/utils/mockData.js中定义模拟数据
### 8.3 开发规范
- **代码风格**使用ESLint和Prettier保持代码风格一致
- **命名规范**:组件名使用大驼峰,变量名使用小驼峰
- **注释规范**关键组件和函数添加JSDoc注释
- **提交规范**使用Git Commitizen保持提交信息规范
## 9. 测试计划
### 9.1 功能测试
- **页面导航**:测试所有页面之间的导航是否正常
- **数据展示**:测试各种数据的展示是否正确
- **交互功能**:测试所有交互元素是否正常响应
- **表单提交**:测试表单验证和提交功能
### 9.2 性能测试
- **页面加载速度**:测试各页面的加载时间
- **交互响应速度**:测试用户操作的响应时间
- **大数据量处理**:测试处理大量数据时的性能
### 9.3 兼容性测试
- **浏览器兼容性**测试主流浏览器Chrome、Firefox、Safari、Edge
- **设备兼容性**:测试不同尺寸的设备
### 9.4 可用性测试
- **用户体验**:评估界面的易用性和直观性
- **错误处理**:测试系统对错误操作的处理
- **帮助信息**:测试系统的帮助和提示信息
## 10. 模拟数据
### 10.1 期货品种列表
```javascript
const futuresList = [
// 金属类
{ code: 'AU', name: '金', type: '金属' },
{ code: 'AG', name: '银', type: '金属' },
{ code: 'CU', name: '铜', type: '金属' },
{ code: 'NI', name: '镍', type: '金属' },
{ code: 'SN', name: '锡', type: '金属' },
{ code: 'AL', name: '铝', type: '金属' },
{ code: 'ZN', name: '锌', type: '金属' },
// 建材类
{ code: 'FG', name: '玻璃', type: '建材' },
{ code: 'SJS', name: '烧碱', type: '建材' },
{ code: 'SCA', name: '纯碱', type: '建材' },
{ code: 'JM', name: '焦煤', type: '建材' },
{ code: 'RB', name: '螺纹钢', type: '建材' },
{ code: 'ALO', name: '氧化铝', type: '建材' },
// 能源化工类
{ code: 'MA', name: '甲醇', type: '能源化工' },
{ code: 'PVC', name: 'PVC', type: '能源化工' },
{ code: 'FU', name: '燃油', type: '能源化工' },
{ code: 'SC', name: '原油', type: '能源化工' },
{ code: 'L', name: '橡胶', type: '能源化工' },
{ code: 'NR', name: '20号胶', type: '能源化工' },
{ code: 'BU', name: '沥青', type: '能源化工' },
{ code: 'LU', name: '低硫燃油', type: '能源化工' },
// 农产品类
{ code: 'P', name: '棕榈油', type: '农产品' },
// 新能源类
{ code: 'LC', name: '碳酸锂', type: '新能源' },
{ code: 'SI', name: '工业硅', type: '新能源' },
{ code: 'PGS', name: '多晶硅', type: '新能源' },
// 金融类
{ code: 'IC', name: '中证500', type: '金融' },
{ code: 'IM', name: '中证1000', type: '金融' },
{ code: 'IH', name: '上证50', type: '金融' }
];
```
### 10.2 品种数据结构
```javascript
const futureData = {
code: 'MA',
name: '甲醇',
fullName: '甲醇-MA605',
currentPrice: 2188.00,
changePercent: -2.35,
atr: 2.35,
adx: 19,
adxStatus: '无趋势/震荡',
winRate: 56,
trends: {
'5MIN': { direction: '看空', status: '空头趋势', rsi: 24 },
'30MIN': { direction: '看空', status: '空头趋势', rsi: 19 },
'1HOUR': { direction: '看空', status: '空头趋势', rsi: 17 },
'1DAY': { direction: '看空', status: '空头趋势', rsi: 30 }
},
indicators: {
macd: '死叉向下',
rsi: '34(中性)',
bollinger: '触及下轨',
kdj: '死叉向下'
},
tradingAdvice: {
entry: 2188,
stopLoss: 2166,
target: 2210
},
riskLevel: '中等',
volatility: '高'
};
```
## 11. 结论
本设计方案提供了AI期货分析系统的完整前端设计包括页面布局、交互流程、响应式设计和技术实现指南。方案基于React + Ant Design技术栈采用浅色简洁的设计风格确保专业、直观的用户体验。
设计方案充分考虑了期货分析系统的专业性要求提供了K线图表、技术指标、热力图等专业数据可视化功能同时确保了系统的易用性和可扩展性。
通过本设计方案开发团队可以快速实现一个功能完整、体验良好的AI期货分析系统前端为期货投资者提供专业的决策辅助工具。

@ -0,0 +1,104 @@
# AI期货分析系统前端设计需求
## 1. 项目概述
AI期货分析系统是基于DeepSeek等大模型和量化分析算法的智能期货决策辅助系统为期货投资者提供多维度市场分析、AI智能研判、全自动数据更新和专业风控管理功能。
## 2. 技术栈
- **前端框架**React + Ant Design
- **响应式设计**:全设备适配(桌面端、平板、移动端)
- **视觉风格**:浅色简洁风格
- **导航方式**:混合导航(左侧菜单 + 顶部导航)
## 3. 核心功能模块
### 3.1 多维度市场分析
- 技术面分析K线形态、技术指标、趋势线等
- 资金面分析(持仓量、成交量、资金流向等)
- 政策面分析(宏观数据、政策变动、新闻情感等)
### 3.2 AI智能研判
- 趋势判断(基于大模型的趋势预测)
- 胜率评估(历史回测、策略表现分析)
- 风险预警(异常波动监测、情绪识别等)
### 3.3 全自动数据更新
- 实时行情数据获取
- 自动复盘每日3次
- 数据处理与管理
### 3.4 专业风控管理
- 止损建议
- 仓位管理
- 换月预警
### 3.5 配置管理
- 数据源配置(多数据源选择与切换)
- AI模型配置大模型选择与参数调优
- 系统配置(分析周期、预警阈值等)
## 4. 页面结构
### 4.1 Dashboard主页
- 多品种概览卡片显示胜率、ATR、ADX等关键指标
- 市场热点分析
- 风险预警提示
- 快速访问常用品种
### 4.2 详情分析页
- K线图表支持多周期切换
- 技术指标分析
- AI智能研判结果
- 风险评估
- 交易建议
### 4.3 风控管理页
- 止损策略设置
- 仓位计算工具
- 换月提醒
- 风险监控面板
### 4.4 配置管理页
- 数据源管理
- AI模型选择与配置
- 系统参数设置
- 用户偏好设置
## 5. 数据可视化需求
- **K线图表**专业的K线图表支持多周期切换
- **技术指标**MA、MACD、KDJ等技术指标的可视化
- **热力图**:多品种对比的热力图展示
- **趋势分析**AI预测趋势的可视化展示
## 6. 模拟数据
### 6.1 期货品种列表
- 金、银、铜、镍、锡、玻璃、烧碱、纯碱、焦煤
- 螺纹钢、氧化铝、甲醇、PVC、燃油、原油、铝、棕榈油
- 碳酸锂、工业硅、橡胶、合成橡胶、锌、20号胶、多晶硅
- 中证1000、中证500、低硫燃油、上证50
### 6.2 数据字段
- 品种名称、代码
- 当前价格、涨跌幅
- ATR平均真实波幅
- ADX平均趋向指数
- 胜率评估
- 多周期趋势判断5MIN、30MIN、1HOUR、1DAY
- 技术指标MACD、RSI、布林带、KDJ等
- 入场价、止损价、目标价
## 7. 交互设计要求
- 响应式布局,适配不同设备尺寸
- 流畅的页面切换和数据加载动画
- 直观的操作流程,减少学习成本
- 专业的金融分析界面风格
- 支持深色/浅色主题切换
## 8. 性能要求
- 实时响应,分析结果秒级返回
- 数据更新延迟<1
- 系统稳定性99.9%以上
- 支持万级用户同时在线
## 9. 设计参考
参考提供的深色主题界面风格,但实现为浅色简洁风格,保持专业的金融分析工具感。

@ -0,0 +1,157 @@
# AI期货分析系统需求分析文档
## 1. 系统概述
AI期货分析系统是基于DeepSeek等大模型和量化分析算法的智能期货决策辅助系统旨在为期货投资者提供多维度市场分析、AI智能研判、全自动数据更新和专业风控管理功能。
## 2. 核心功能模块
### 2.1 多维度市场分析
**技术面分析**
- K线形态识别与分析
- 技术指标计算与解读MA、MACD、KDJ、RSI等
- 趋势线与支撑阻力位自动绘制
- 量价关系分析
**资金面分析**
- 持仓量变化监测
- 成交量分析与资金流向追踪
- 主力资金动向识别
- 多空力量对比分析
**政策面分析**
- 宏观经济数据自动采集与分析
- 政策法规变动监测
- 行业新闻情感分析
- 突发事件影响评估
### 2.2 AI智能研判
**趋势判断**
- 基于大模型的市场趋势预测
- 多周期趋势一致性分析
- 趋势反转信号识别
- 波动率预测
**胜率评估**
- 历史数据回测与胜率计算
- 不同市场环境下的策略表现分析
- 风险收益比评估
- 预期收益预测
**风险预警**
- 异常价格波动监测
- 市场情绪极端状态识别
- 流动性风险评估
- 黑天鹅事件预警
### 2.3 全自动数据更新
**数据采集**
- 实时行情数据自动获取
- 历史数据定期更新
- 基本面数据自动采集
- 外部数据源集成
**数据处理**
- 数据清洗与标准化
- 数据质量监控
- 数据一致性检查
- 数据存储与管理
**自动复盘**
- 每日3次自动复盘早、中、晚
- 盘前预测与盘后总结
- 关键事件自动标记
- 历史表现分析与优化
### 2.4 专业风控管理
**止损建议**
- 动态止损点位计算
- 不同策略的止损策略推荐
- 止损执行监控与提醒
- 止损效果评估
**仓位管理**
- 基于风险偏好的仓位计算
- 资金分配优化建议
- 杠杆使用控制
- 多品种仓位平衡
**换月预警**
- 合约到期日提醒
- 主力合约切换监测
- 换月成本分析
- 换月策略建议
### 2.5 配置管理
**数据源配置**
- 多数据源选择与切换
- 数据源优先级设置
- 数据源参数配置API密钥、访问频率等
- 数据源健康状态监控
**AI模型配置**
- 大模型选择与切换DeepSeek等
- 模型参数调优
- 模型权重管理
- 模型性能评估与对比
**系统配置**
- 分析周期设置
- 预警阈值配置
- 个性化偏好设置
- 系统参数优化
## 3. 系统架构
### 3.1 技术架构
- **前端**Web应用 + 移动App
- **后端**:云端部署的微服务架构
- **数据层**:实时行情数据库 + 历史数据库 + 分析结果库
- **AI层**DeepSeek等大模型 + 量化分析算法库
- **接口层**API网关 + 第三方数据接口
### 3.2 部署方式
- 云端SaaS模式无需本地安装
- 多区域部署,确保实时响应
- 弹性扩容,应对高并发需求
- 数据加密存储,保障安全
## 4. 目标用户
- **个人投资者**:提供直观的决策辅助工具,降低交易门槛
- **专业交易员**:提供深度分析功能,支持复杂策略开发
- **混合型**:同时满足不同层次用户的需求,提供个性化功能
## 5. 性能要求
- **响应时间**:实时响应,分析结果秒级返回
- **数据更新**:实时行情数据延迟<1
- **系统稳定性**99.9%以上的可用性
- **并发处理**:支持万级用户同时在线
## 6. 数据安全
- 数据传输加密
- 用户身份认证与授权
- 敏感信息保护
- 数据备份与恢复机制
## 7. 实施路径
1. **需求细化与原型设计**详细功能规格制定UI/UX设计
2. **核心功能开发**数据采集、分析算法、AI模型集成
3. **系统集成与测试**:各模块集成,性能测试,安全测试
4. **试运行与优化**:内部测试,用户反馈收集,系统优化
5. **正式上线与迭代**:持续功能更新,性能优化,用户体验改进
## 8. 预期成果
- 为期货投资者提供全方位的市场分析工具
- 提高交易决策的科学性与准确性
- 降低投资风险,优化收益表现
- 推动期货市场的智能化发展

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>alphafuturespro</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,16 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>new-project</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,35 @@
{
"name": "new-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.11.2",
"antd": "^6.3.0",
"lightweight-charts": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"vite": "^8.0.0-beta.13"
},
"overrides": {
"vite": "^8.0.0-beta.13"
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

@ -0,0 +1,35 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}
export default App

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,68 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

3810
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,35 @@
{
"name": "new-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.11.2",
"antd": "^6.3.0",
"lightweight-charts": "^5.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"vite": "^8.0.0-beta.13"
},
"overrides": {
"vite": "^8.0.0-beta.13"
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,64 @@
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f0f2f5;
color: #262626;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
/* 主应用容器 */
.App {
min-height: 100vh;
}
/* 通用标题样式 */
h2 {
font-size: 24px;
font-weight: 600;
color: #262626;
margin-bottom: 24px;
}
/* 通用卡片样式 */
.ant-card {
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
/* 通用按钮样式 */
.ant-btn {
border-radius: 4px;
}
/* 通用表单样式 */
.ant-form {
width: 100%;
}
/* 通用表格样式 */
.ant-table {
border-radius: 4px;
overflow: hidden;
}
/* 响应式设计 */
@media (max-width: 768px) {
h2 {
font-size: 20px;
margin-bottom: 16px;
}
}

@ -0,0 +1,29 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';
import MainLayout from './components/layout/MainLayout';
import Dashboard from './pages/dashboard/Dashboard';
import Detail from './pages/detail/Detail';
import RiskControl from './pages/risk-control/RiskControl';
import Config from './pages/config/Config';
import './App.css';
function App() {
return (
<Provider store={store}>
<Router>
<MainLayout>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/detail" element={<Detail />} />
<Route path="/risk-control" element={<RiskControl />} />
<Route path="/config" element={<Config />} />
</Routes>
</MainLayout>
</Router>
</Provider>
);
}
export default App;

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1,69 @@
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
background: #1890ff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
}
.logo {
color: #fff;
font-size: 20px;
font-weight: bold;
margin: 0;
}
.header-right {
display: flex;
align-items: center;
}
.header-search {
margin-right: 16px;
}
.dark-mode-toggle {
margin: 0 16px;
}
.content {
padding: 24px;
background: #f0f2f5;
overflow: auto;
}
.content-inner {
background: #fff;
padding: 24px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
padding: 0 16px;
}
.logo {
font-size: 16px;
}
.header-search {
width: 150px !important;
}
.content {
padding: 16px;
}
.content-inner {
padding: 16px;
}
}

@ -0,0 +1,92 @@
import React, { useState } from 'react';
import { Layout, Menu, Button, Input, Avatar, Badge, Switch, ConfigProvider } from 'antd';
import { SearchOutlined, BellOutlined, UserOutlined, MenuFoldOutlined, MenuUnfoldOutlined, HomeOutlined, BarChartOutlined, SafetyOutlined, SettingOutlined } from '@ant-design/icons';
import { Link, useLocation } from 'react-router-dom';
import './MainLayout.css';
const { Header, Sider, Content } = Layout;
const { Search } = Input;
const MainLayout = ({ children }) => {
const [collapsed, setCollapsed] = useState(false);
const [darkMode, setDarkMode] = useState(false);
const location = useLocation();
const toggleCollapsed = () => {
setCollapsed(!collapsed);
};
const toggleDarkMode = () => {
setDarkMode(!darkMode);
};
const getSelectedKey = () => {
const path = location.pathname;
if (path === '/') return '1';
if (path === '/detail') return '2';
if (path === '/risk-control') return '3';
if (path === '/config') return '4';
return '1';
};
return (
<ConfigProvider theme={darkMode ? { token: { colorScheme: 'dark' } } : {}}>
<Layout style={{ minHeight: '100vh' }}>
<Header className="header">
<div className="header-left">
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={toggleCollapsed}
style={{ marginRight: 16, color: '#fff' }}
/>
<h1 className="logo">AI期货分析系统</h1>
</div>
<div className="header-right">
<Search
placeholder="搜索品种"
className="header-search"
style={{ width: 200 }}
/>
<Badge count={3} style={{ marginLeft: 16 }}>
<Button type="text" icon={<BellOutlined />} style={{ color: '#fff' }} />
</Badge>
<span className="dark-mode-toggle">
<Switch checked={darkMode} onChange={toggleDarkMode} checkedChildren="暗" unCheckedChildren="亮" />
</span>
<Avatar icon={<UserOutlined />} style={{ marginLeft: 16 }} />
</div>
</Header>
<Layout>
<Sider width={200} theme="light" trigger={null} collapsible collapsed={collapsed}>
<Menu
mode="inline"
selectedKeys={[getSelectedKey()]}
style={{ height: '100%', borderRight: 0 }}
>
<Menu.Item key="1" icon={<HomeOutlined />}>
<Link to="/">Dashboard</Link>
</Menu.Item>
<Menu.Item key="2" icon={<BarChartOutlined />}>
<Link to="/detail">详情分析</Link>
</Menu.Item>
<Menu.Item key="3" icon={<SafetyOutlined />}>
<Link to="/risk-control">风控管理</Link>
</Menu.Item>
<Menu.Item key="4" icon={<SettingOutlined />}>
<Link to="/config">配置管理</Link>
</Menu.Item>
</Menu>
</Sider>
<Content className="content">
<div className="content-inner">
{children}
</div>
</Content>
</Layout>
</Layout>
</ConfigProvider>
);
};
export default MainLayout;

@ -0,0 +1,36 @@
/* 全局样式 */
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
#root {
width: 100%;
min-height: 100vh;
}
/* 移除默认样式 */
ul, ol {
list-style: none;
}
/* 确保antd样式正确加载 */
@import 'antd/dist/reset.css';

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

@ -0,0 +1,47 @@
.config {
padding: 0;
}
.config h2 {
margin: 0 0 24px 0;
color: #262626;
}
.config-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
border-radius: 4px;
}
/* 参数设置项 */
.param-item {
padding: 16px;
background: #fafafa;
border-radius: 4px;
margin-bottom: 16px;
}
.param-item label {
display: block;
margin-bottom: 16px;
font-size: 14px;
color: #262626;
}
/* 配置操作按钮 */
.config-actions {
margin-top: 24px;
display: flex;
justify-content: flex-end;
}
/* 响应式设计 */
@media (max-width: 768px) {
.config-actions {
flex-direction: column;
gap: 8px;
}
.config-actions Button {
width: 100%;
}
}

@ -0,0 +1,315 @@
import React, { useState } from 'react';
import { Card, Row, Col, Form, Input, Button, Select, Switch, Slider, Tag, Alert, Table } from 'antd';
import { SettingOutlined, DatabaseOutlined, RobotOutlined, SlidersOutlined } from '@ant-design/icons';
import './Config.css';
const { Option } = Select;
const { Item } = Form;
const Config = () => {
const [form] = Form.useForm();
//
const dataSources = [
{ id: 1, name: 'Wind', status: 'online', responseTime: '120ms', priority: 1, enabled: true },
{ id: 2, name: '同花顺', status: 'online', responseTime: '150ms', priority: 2, enabled: true },
{ id: 3, name: '东方财富', status: 'online', responseTime: '180ms', priority: 3, enabled: true },
{ id: 4, name: '新浪财经', status: 'offline', responseTime: '-', priority: 4, enabled: false }
];
// AI
const aiModels = [
{ id: 1, name: 'DeepSeek', accuracy: '85%', responseTime: '250ms', enabled: true },
{ id: 2, name: 'GPT-4', accuracy: '88%', responseTime: '350ms', enabled: false },
{ id: 3, name: 'Claude', accuracy: '82%', responseTime: '200ms', enabled: false },
{ id: 4, name: '自定义模型', accuracy: '78%', responseTime: '150ms', enabled: false }
];
//
const systemParams = {
refreshInterval: 30, //
alertThreshold: 70, // %
maxPositionSize: 50, // %
backtestDays: 90 //
};
const [params, setParams] = useState(systemParams);
//
const handleParamChange = (key, value) => {
setParams(prev => ({ ...prev, [key]: value }));
};
//
const handleSubmit = (values) => {
console.log('配置保存:', values);
//
Alert.success('配置已保存');
};
//
const getDataSourceStatusColor = (status) => {
return status === 'online' ? 'green' : 'red';
};
//
const getDataSourceStatusText = (status) => {
return status === 'online' ? '在线' : '离线';
};
return (
<div className="config">
<h2>配置管理</h2>
{/* 数据源配置 */}
<Card
title={<span><DatabaseOutlined /> 数据源配置</span>}
className="config-card"
style={{ marginBottom: 24 }}
>
<Table
dataSource={dataSources}
columns={[
{
title: '数据源',
dataIndex: 'name',
key: 'name'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status) => (
<Tag color={getDataSourceStatusColor(status)}>
{getDataSourceStatusText(status)}
</Tag>
)
},
{
title: '响应时间',
dataIndex: 'responseTime',
key: 'responseTime'
},
{
title: '优先级',
dataIndex: 'priority',
key: 'priority',
render: (priority) => (
<Select
defaultValue={priority}
style={{ width: 80 }}
onChange={() => {}}
>
{[1, 2, 3, 4, 5].map(p => (
<Option key={p} value={p}>{p}</Option>
))}
</Select>
)
},
{
title: '启用',
dataIndex: 'enabled',
key: 'enabled',
render: (enabled) => (
<Switch checked={enabled} onChange={() => {}} />
)
},
{
title: '操作',
key: 'action',
render: () => (
<Button type="link">编辑</Button>
)
}
]}
rowKey="id"
/>
<Button type="primary" style={{ marginTop: 16 }}>
添加数据源
</Button>
</Card>
{/* AI模型配置 */}
<Card
title={<span><RobotOutlined /> AI模型配置</span>}
className="config-card"
style={{ marginBottom: 24 }}
>
<Table
dataSource={aiModels}
columns={[
{
title: '模型名称',
dataIndex: 'name',
key: 'name'
},
{
title: '准确率',
dataIndex: 'accuracy',
key: 'accuracy'
},
{
title: '响应时间',
dataIndex: 'responseTime',
key: 'responseTime'
},
{
title: '默认模型',
dataIndex: 'enabled',
key: 'enabled',
render: (enabled) => (
<Switch checked={enabled} onChange={() => {}} />
)
},
{
title: '操作',
key: 'action',
render: () => (
<Button type="link">配置</Button>
)
}
]}
rowKey="id"
/>
{/* 模型参数调优 */}
<Card title="模型参数调优" style={{ marginTop: 24 }}>
<Form layout="vertical">
<Row gutter={[16, 16]}>
<Col span={8}>
<Item label="预测周期">
<Select defaultValue="1D">
<Option value="1H">1小时</Option>
<Option value="4H">4小时</Option>
<Option value="1D">1</Option>
<Option value="1W">1</Option>
</Select>
</Item>
</Col>
<Col span={8}>
<Item label="置信度阈值">
<Input addonAfter="%" type="number" defaultValue={70} min={50} max={95} />
</Item>
</Col>
<Col span={8}>
<Item label="历史数据长度">
<Input addonAfter="天" type="number" defaultValue={90} min={30} max={365} />
</Item>
</Col>
</Row>
<Button type="primary" style={{ marginTop: 8 }}>
应用参数
</Button>
</Form>
</Card>
</Card>
{/* 系统配置 */}
<Card
title={<span><SlidersOutlined /> 系统配置</span>}
className="config-card"
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Row gutter={[16, 16]}>
<Col span={12}>
<div className="param-item">
<label>数据刷新间隔: {params.refreshInterval}</label>
<Slider
min={10}
max={300}
step={10}
value={params.refreshInterval}
onChange={(value) => handleParamChange('refreshInterval', value)}
marks={{
10: '10s',
300: '5m'
}}
/>
</div>
</Col>
<Col span={12}>
<div className="param-item">
<label>预警阈值: {params.alertThreshold}%</label>
<Slider
min={50}
max={90}
step={5}
value={params.alertThreshold}
onChange={(value) => handleParamChange('alertThreshold', value)}
marks={{
50: '50%',
90: '90%'
}}
/>
</div>
</Col>
<Col span={12}>
<div className="param-item">
<label>最大仓位比例: {params.maxPositionSize}%</label>
<Slider
min={10}
max={100}
step={5}
value={params.maxPositionSize}
onChange={(value) => handleParamChange('maxPositionSize', value)}
marks={{
10: '10%',
100: '100%'
}}
/>
</div>
</Col>
<Col span={12}>
<div className="param-item">
<label>回测天数: {params.backtestDays}</label>
<Slider
min={30}
max={365}
step={30}
value={params.backtestDays}
onChange={(value) => handleParamChange('backtestDays', value)}
marks={{
30: '30天',
365: '1年'
}}
/>
</div>
</Col>
</Row>
{/* 其他系统设置 */}
<Card title="其他设置" style={{ marginTop: 24 }}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Item label="自动复盘">
<Switch defaultChecked />
</Item>
</Col>
<Col span={8}>
<Item label="邮件通知">
<Switch />
</Item>
</Col>
<Col span={8}>
<Item label="短信通知">
<Switch />
</Item>
</Col>
</Row>
</Card>
<div className="config-actions">
<Button type="default" style={{ marginRight: 8 }}>
恢复默认
</Button>
<Button type="primary" htmlType="submit">
保存配置
</Button>
</div>
</Form>
</Card>
</div>
);
};
export default Config;

@ -0,0 +1,231 @@
.dashboard {
padding: 0;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.dashboard-header h2 {
margin: 0;
color: #262626;
}
.dashboard-header-actions {
display: flex;
align-items: center;
}
.section-title {
margin: 0 0 16px 0;
color: #262626;
font-size: 18px;
}
.dashboard-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
border-radius: 4px;
}
/* 市场热点 */
.market-hotspots {
display: flex;
flex-direction: column;
gap: 12px;
}
.hotspot-item {
display: flex;
align-items: center;
padding: 12px;
background: #fafafa;
border-radius: 4px;
}
.hotspot-rank {
width: 24px;
height: 24px;
background: #1890ff;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 12px;
}
.hotspot-info {
flex: 1;
}
.hotspot-name {
font-weight: 500;
margin-bottom: 4px;
}
.hotspot-change {
font-size: 14px;
}
.hotspot-winrate {
margin-left: 12px;
}
/* 风险预警 */
.risk-alerts {
display: flex;
flex-direction: column;
gap: 12px;
}
/* AI研判 */
.ai-analysis {
display: flex;
flex-direction: column;
gap: 20px;
}
.ai-overall {
display: flex;
gap: 40px;
margin-bottom: 16px;
}
.ai-factors h4,
.ai-recommendations h4 {
margin: 0 0 8px 0;
color: #262626;
}
.factor-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ai-recommendations ul {
margin: 0;
padding-left: 20px;
}
.ai-recommendations li {
margin-bottom: 4px;
}
/* 品种卡片 */
.future-card {
height: 100%;
transition: all 0.3s ease;
cursor: pointer;
}
.future-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.future-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.future-name h4 {
margin: 0 0 4px 0;
color: #262626;
}
.future-change {
font-size: 14px;
font-weight: 500;
}
.future-price {
font-size: 20px;
font-weight: bold;
color: #262626;
margin-bottom: 16px;
}
.future-metrics {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.metric-item {
display: flex;
flex-direction: column;
align-items: center;
}
.metric-label {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 4px;
}
.metric-value {
font-size: 14px;
font-weight: 500;
color: #262626;
}
.future-trends {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.trend-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
background: #fafafa;
border-radius: 4px;
}
.trend-period {
font-size: 12px;
color: #8c8c8c;
margin-bottom: 4px;
}
/* 加载容器 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.dashboard-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.dashboard-header-actions {
width: 100%;
justify-content: space-between;
}
.ai-overall {
flex-direction: column;
gap: 20px;
}
.future-trends {
grid-template-columns: 1fr;
}
}

@ -0,0 +1,287 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Row, Col, Statistic, Button, Select, Tag, message, Spin, Alert } from 'antd';
import { ReloadOutlined, ArrowUpOutlined, ArrowDownOutlined, FireOutlined, AlertOutlined, RobotOutlined } from '@ant-design/icons';
import { fetchFuturesOverview, fetchRiskAlerts, fetchAIMarketAnalysis } from '../../store/futuresSlice';
import { useNavigate } from 'react-router-dom';
import './Dashboard.css';
const { Option } = Select;
const Dashboard = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { overview, riskAlerts, aiAnalysis, loading } = useSelector(state => state.futures);
const [filterType, setFilterType] = useState('all');
const [sortBy, setSortBy] = useState('winRate');
useEffect(() => {
dispatch(fetchFuturesOverview());
dispatch(fetchRiskAlerts());
dispatch(fetchAIMarketAnalysis());
}, [dispatch]);
const handleRefresh = () => {
dispatch(fetchFuturesOverview());
dispatch(fetchRiskAlerts());
dispatch(fetchAIMarketAnalysis());
message.success('数据已刷新');
};
const handleFutureClick = (future) => {
navigate(`/detail?code=${future.code}&name=${future.name}`);
};
const getChangeColor = (changePercent) => {
return changePercent >= 0 ? '#52c41a' : '#ff4d4f';
};
const getChangeIcon = (changePercent) => {
return changePercent >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />;
};
const getTrendColor = (direction) => {
if (direction === '看多') return '#52c41a';
if (direction === '看空') return '#ff4d4f';
return '#faad14';
};
const getRiskLevelColor = (level) => {
if (level === '高') return '#ff4d4f';
if (level === '中等') return '#faad14';
return '#52c41a';
};
const filteredAndSortedData = () => {
let data = [...overview];
//
if (filterType !== 'all') {
data = data.filter(item => {
//
return true;
});
}
//
data.sort((a, b) => {
if (sortBy === 'winRate') {
return b.winRate - a.winRate;
} else if (sortBy === 'changePercent') {
return b.changePercent - a.changePercent;
} else if (sortBy === 'atr') {
return b.atr - a.atr;
}
return 0;
});
return data;
};
if (loading && overview.length === 0) {
return (
<div className="loading-container">
<Spin size="large" tip="加载数据中..." />
</div>
);
}
return (
<div className="dashboard">
{/* 页面头部 */}
<div className="dashboard-header">
<h2>市场概览</h2>
<div className="dashboard-header-actions">
<Select
defaultValue="all"
style={{ width: 120, marginRight: 16 }}
onChange={setFilterType}
>
<Option value="all">全部类型</Option>
<Option value="metal">金属</Option>
<Option value="building">建材</Option>
<Option value="energy">能源化工</Option>
</Select>
<Select
defaultValue="winRate"
style={{ width: 120, marginRight: 16 }}
onChange={setSortBy}
>
<Option value="winRate">胜率</Option>
<Option value="changePercent">涨跌幅</Option>
<Option value="atr">波动率</Option>
</Select>
<Button
type="primary"
icon={<ReloadOutlined />}
onClick={handleRefresh}
>
刷新数据
</Button>
</div>
</div>
{/* 市场热点和风险预警 */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
{/* 市场热点 */}
<Col span={12}>
<Card
title={<span><FireOutlined /> 市场热点</span>}
className="dashboard-card"
>
<div className="market-hotspots">
{filteredAndSortedData().slice(0, 5).map((item, index) => (
<div key={item.code} className="hotspot-item">
<div className="hotspot-rank">{index + 1}</div>
<div className="hotspot-info">
<div className="hotspot-name">{item.name} ({item.code})</div>
<div className="hotspot-change" style={{ color: getChangeColor(item.changePercent) }}>
{getChangeIcon(item.changePercent)} {Math.abs(item.changePercent)}%
</div>
</div>
<div className="hotspot-winrate">
<Tag color={item.winRate > 60 ? 'green' : item.winRate > 40 ? 'orange' : 'red'}>
胜率 {item.winRate}%
</Tag>
</div>
</div>
))}
</div>
</Card>
</Col>
{/* 风险预警 */}
<Col span={12}>
<Card
title={<span><AlertOutlined /> 风险预警</span>}
className="dashboard-card"
>
<div className="risk-alerts">
{riskAlerts.map(alert => (
<Alert
key={alert.id}
message={alert.title}
description={alert.message}
type={alert.level === '高' ? 'error' : alert.level === '中等' ? 'warning' : 'info'}
showIcon
style={{ marginBottom: 12 }}
/>
))}
</div>
</Card>
</Col>
</Row>
{/* AI研判 */}
<Card
title={<span><RobotOutlined /> AI市场研判</span>}
className="dashboard-card"
style={{ marginBottom: 24 }}
>
<div className="ai-analysis">
<div className="ai-overall">
<Statistic
title="整体趋势"
value={aiAnalysis?.overallTrend || '分析中'}
valueStyle={{ color: '#1890ff' }}
/>
<Statistic
title="置信度"
value={aiAnalysis?.confidence || 0}
suffix="%"
valueStyle={{ color: '#1890ff' }}
/>
</div>
<div className="ai-factors">
<h4>关键因素</h4>
<div className="factor-tags">
{aiAnalysis?.keyFactors?.map((factor, index) => (
<Tag key={index} color="blue">{factor}</Tag>
))}
</div>
</div>
<div className="ai-recommendations">
<h4>操作建议</h4>
<ul>
{aiAnalysis?.recommendations?.map((recommendation, index) => (
<li key={index}>{recommendation}</li>
))}
</ul>
</div>
</div>
</Card>
{/* 品种概览 */}
<h3 className="section-title">品种概览</h3>
<Row gutter={[16, 16]}>
{filteredAndSortedData().map(item => (
<Col xs={24} sm={12} md={8} lg={6} key={item.code}>
<Card
className="future-card"
hoverable
onClick={() => handleFutureClick(item)}
>
<div className="future-header">
<div className="future-name">
<h4>{item.name}</h4>
<Tag size="small">{item.code}</Tag>
</div>
<div className="future-change" style={{ color: getChangeColor(item.changePercent) }}>
{getChangeIcon(item.changePercent)} {Math.abs(item.changePercent)}%
</div>
</div>
<div className="future-price">
¥{item.currentPrice.toFixed(2)}
</div>
<div className="future-metrics">
<div className="metric-item">
<span className="metric-label">胜率</span>
<span className="metric-value">{item.winRate}%</span>
</div>
<div className="metric-item">
<span className="metric-label">ATR</span>
<span className="metric-value">{item.atr}</span>
</div>
<div className="metric-item">
<span className="metric-label">ADX</span>
<span className="metric-value">{item.adx}</span>
</div>
</div>
<div className="future-trends">
<div className="trend-item">
<span className="trend-period">5MIN</span>
<Tag size="small" color={getTrendColor(item.trends['5MIN'].direction)}>
{item.trends['5MIN'].direction}
</Tag>
</div>
<div className="trend-item">
<span className="trend-period">30MIN</span>
<Tag size="small" color={getTrendColor(item.trends['30MIN'].direction)}>
{item.trends['30MIN'].direction}
</Tag>
</div>
<div className="trend-item">
<span className="trend-period">1HOUR</span>
<Tag size="small" color={getTrendColor(item.trends['1HOUR'].direction)}>
{item.trends['1HOUR'].direction}
</Tag>
</div>
<div className="trend-item">
<span className="trend-period">1DAY</span>
<Tag size="small" color={getTrendColor(item.trends['1DAY'].direction)}>
{item.trends['1DAY'].direction}
</Tag>
</div>
</div>
</Card>
</Col>
))}
</Row>
</div>
);
};
export default Dashboard;

@ -0,0 +1,148 @@
.detail {
padding: 0;
}
.detail-header {
margin-bottom: 24px;
}
.detail-header h2 {
margin: 8px 0 0 0;
color: #262626;
}
.detail-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
border-radius: 4px;
}
/* 加载容器 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
/* 错误容器 */
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400px;
}
/* 图表标题 */
.chart-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.chart-title h3 {
margin: 0;
}
/* K线图表 */
.kline-chart {
width: 100%;
height: 400px;
}
/* 趋势卡片 */
.trend-card {
height: 100%;
text-align: center;
}
.trend-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.trend-header h4 {
margin: 0;
color: #262626;
}
.trend-status {
font-size: 14px;
margin-bottom: 8px;
color: #262626;
}
.trend-rsi {
font-size: 14px;
color: #8c8c8c;
}
/* 技术指标 */
.indicator-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
background: #fafafa;
border-radius: 4px;
height: 100%;
}
.indicator-label {
font-size: 14px;
color: #8c8c8c;
margin-bottom: 8px;
}
.indicator-value {
font-size: 16px;
font-weight: 500;
color: #262626;
}
/* 风险评估 */
.risk-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
background: #fafafa;
border-radius: 4px;
height: 100%;
}
.risk-label {
font-size: 14px;
color: #262626;
}
/* 响应式设计 */
@media (max-width: 768px) {
.detail-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.chart-title {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.kline-chart {
height: 300px;
}
.trend-card {
margin-bottom: 16px;
}
.indicator-item,
.risk-item {
margin-bottom: 16px;
}
}

@ -0,0 +1,377 @@
import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Row, Col, Button, Select, Tag, Statistic, Alert, Spin } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, LineChartOutlined, BarChartOutlined, AlertOutlined, CalculatorOutlined } from '@ant-design/icons';
import { fetchFutureDetail } from '../../store/futuresSlice';
import { useLocation, useNavigate } from 'react-router-dom';
import { generateKlineData } from '../../utils/mockData';
import './Detail.css';
// TradingView Lightweight Charts
import { createChart } from 'lightweight-charts';
const { Option } = Select;
const Detail = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const chartRef = useRef(null);
const chartInstance = useRef(null);
const { selectedFuture, loading } = useSelector(state => state.futures);
const [timeframe, setTimeframe] = useState('1D');
// URL
const getQueryParams = () => {
const params = new URLSearchParams(location.search);
return {
code: params.get('code') || 'MA',
name: params.get('name') || '甲醇'
};
};
const { code, name } = getQueryParams();
useEffect(() => {
//
dispatch(fetchFutureDetail({ code, name }));
}, [dispatch, code, name]);
useEffect(() => {
// K线
if (chartRef.current && selectedFuture) {
if (chartInstance.current) {
chartInstance.current.destroy();
}
const chart = createChart(chartRef.current, {
width: chartRef.current.clientWidth,
height: 400,
layout: {
backgroundColor: '#fff',
textColor: '#262626'
},
grid: {
vertLines: {
color: '#f0f0f0'
},
horzLines: {
color: '#f0f0f0'
}
},
priceScale: {
borderColor: '#f0f0f0'
},
timeScale: {
borderColor: '#f0f0f0',
timeVisible: true,
secondsVisible: false
}
});
// K线
const candlestickSeries = chart.addCandlestickSeries({
upColor: '#52c41a',
downColor: '#ff4d4f',
borderUpColor: '#52c41a',
borderDownColor: '#ff4d4f',
wickUpColor: '#52c41a',
wickDownColor: '#ff4d4f'
});
// K线
const klineData = generateKlineData(30);
candlestickSeries.setData(klineData);
//
const volumeSeries = chart.addHistogramSeries({
color: '#82ca9d',
lineWidth: 1,
priceScaleId: '',
scaleMargins: {
top: 0.8,
bottom: 0
}
});
const volumeData = klineData.map(item => ({
time: item.time,
value: item.volume,
color: item.close >= item.open ? '#52c41a' : '#ff4d4f'
}));
volumeSeries.setData(volumeData);
//
chart.timeScale().fitContent();
chartInstance.current = chart;
//
const handleResize = () => {
if (chartInstance.current) {
chartInstance.current.resize(chartRef.current.clientWidth, 400);
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance.current) {
chartInstance.current.destroy();
}
};
}
}, [selectedFuture]);
const handleBack = () => {
navigate('/');
};
const getChangeColor = (changePercent) => {
return changePercent >= 0 ? '#52c41a' : '#ff4d4f';
};
const getChangeIcon = (changePercent) => {
return changePercent >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />;
};
const getTrendColor = (direction) => {
if (direction === '看多') return '#52c41a';
if (direction === '看空') return '#ff4d4f';
return '#faad14';
};
if (loading) {
return (
<div className="loading-container">
<Spin size="large" tip="加载数据中..." />
</div>
);
}
if (!selectedFuture) {
return (
<div className="error-container">
<Alert message="未找到品种数据" type="error" />
<Button type="primary" onClick={handleBack} style={{ marginTop: 16 }}>
返回主页
</Button>
</div>
);
}
return (
<div className="detail">
{/* 页面头部 */}
<div className="detail-header">
<Button type="default" onClick={handleBack} style={{ marginBottom: 16 }}>
返回主页
</Button>
<h2>{selectedFuture.fullName}</h2>
</div>
{/* 基本信息 */}
<Card className="detail-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Statistic
title="当前价格"
value={selectedFuture.currentPrice}
valueStyle={{ color: '#262626' }}
/>
</Col>
<Col span={8}>
<Statistic
title="涨跌幅"
value={Math.abs(selectedFuture.changePercent)}
suffix="%"
valueStyle={{ color: getChangeColor(selectedFuture.changePercent) }}
prefix={getChangeIcon(selectedFuture.changePercent)}
/>
</Col>
<Col span={8}>
<Statistic
title="胜率"
value={selectedFuture.winRate}
suffix="%"
valueStyle={{ color: selectedFuture.winRate > 60 ? '#52c41a' : selectedFuture.winRate > 40 ? '#faad14' : '#ff4d4f' }}
/>
</Col>
<Col span={8}>
<Statistic
title="ATR"
value={selectedFuture.atr}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="ADX"
value={selectedFuture.adx}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="趋势状态"
value={selectedFuture.adxStatus}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
</Row>
</Card>
{/* K线图表 */}
<Card
title={
<div className="chart-title">
<LineChartOutlined /> K线图表
<Select
defaultValue="1D"
style={{ width: 120, marginLeft: 16 }}
onChange={setTimeframe}
>
<Option value="5MIN">5分钟</Option>
<Option value="30MIN">30分钟</Option>
<Option value="1H">1小时</Option>
<Option value="1D">1</Option>
<Option value="1W">1</Option>
</Select>
</div>
}
className="detail-card"
style={{ marginBottom: 24 }}
>
<div ref={chartRef} className="kline-chart"></div>
</Card>
{/* 多周期趋势分析 */}
<Card
title={<span><BarChartOutlined /> 多周期趋势分析</span>}
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
{Object.entries(selectedFuture.trends).map(([period, trend]) => (
<Col span={6} key={period}>
<Card className="trend-card">
<div className="trend-header">
<h4>{period}</h4>
<Tag color={getTrendColor(trend.direction)}>
{trend.direction}
</Tag>
</div>
<div className="trend-status">
{trend.status}
</div>
<div className="trend-rsi">
RSI: {trend.rsi}
</div>
</Card>
</Col>
))}
</Row>
</Card>
{/* 技术指标 */}
<Card
title="技术指标"
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">MACD</div>
<div className="indicator-value">{selectedFuture.indicators.macd}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">RSI</div>
<div className="indicator-value">{selectedFuture.indicators.rsi}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">布林带</div>
<div className="indicator-value">{selectedFuture.indicators.bollinger}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">KDJ</div>
<div className="indicator-value">{selectedFuture.indicators.kdj}</div>
</div>
</Col>
</Row>
</Card>
{/* 交易建议 */}
<Card
title={<span><CalculatorOutlined /> 交易建议</span>}
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
<Col span={8}>
<Statistic
title="入场价"
value={selectedFuture.tradingAdvice.entry}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="止损价"
value={selectedFuture.tradingAdvice.stopLoss}
valueStyle={{ color: '#ff4d4f' }}
/>
</Col>
<Col span={8}>
<Statistic
title="目标价"
value={selectedFuture.tradingAdvice.target}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
</Row>
</Card>
{/* 风险评估 */}
<Card
title={<span><AlertOutlined /> 风险评估</span>}
className="detail-card"
>
<Row gutter={[16, 16]}>
<Col span={12}>
<div className="risk-item">
<div className="risk-label">风险等级</div>
<Tag color={selectedFuture.riskLevel === '高' ? 'red' : selectedFuture.riskLevel === '中等' ? 'orange' : 'green'}>
{selectedFuture.riskLevel}
</Tag>
</div>
</Col>
<Col span={12}>
<div className="risk-item">
<div className="risk-label">波动率</div>
<Tag color={selectedFuture.volatility === '高' ? 'red' : selectedFuture.volatility === '中等' ? 'orange' : 'green'}>
{selectedFuture.volatility}
</Tag>
</div>
</Col>
</Row>
<Alert
message="风险提示"
description="期货交易具有高风险,请根据自身风险承受能力合理控制仓位,严格执行止损策略。"
type="warning"
showIcon
style={{ marginTop: 16 }}
/>
</Card>
</div>
);
};
export default Detail;

@ -0,0 +1,74 @@
.risk-control {
padding: 0;
}
.risk-control h2 {
margin: 0 0 24px 0;
color: #262626;
}
.risk-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
border-radius: 4px;
}
/* 仓位计算结果 */
.position-result {
display: flex;
justify-content: space-around;
margin-top: 16px;
padding: 24px;
background: #fafafa;
}
/* 风险偏好设置 */
.risk-preference {
padding: 16px;
}
.risk-slider {
display: flex;
align-items: center;
gap: 16px;
}
.risk-slider label {
font-size: 14px;
color: #262626;
white-space: nowrap;
}
.risk-slider .ant-slider {
flex: 1;
}
/* 风险监控指标 */
.risk-metric-card {
height: 100%;
text-align: center;
}
.risk-level {
margin-top: 16px;
display: flex;
justify-content: center;
}
/* 响应式设计 */
@media (max-width: 768px) {
.position-result {
flex-direction: column;
gap: 16px;
align-items: center;
}
.risk-slider {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.risk-slider .ant-slider {
width: 100%;
}
}

@ -0,0 +1,275 @@
import React, { useState } from 'react';
import { Card, Row, Col, Form, Input, Button, Select, Slider, Tag, Alert, Statistic, Table } from 'antd';
import { SafetyOutlined, CalculatorOutlined, CalendarOutlined, BarChartOutlined } from '@ant-design/icons';
import './RiskControl.css';
const { Option } = Select;
const { Item } = Form;
const RiskControl = () => {
const [form] = Form.useForm();
const [positionSize, setPositionSize] = useState(0);
const [riskLevel, setRiskLevel] = useState(50);
//
const stopLossStrategies = [
{ value: 'fixed', label: '固定止损' },
{ value: 'moving', label: '移动止损' },
{ value: 'percent', label: '百分比止损' },
{ value: 'volatility', label: '波动率止损' }
];
//
const rolloverAlerts = [
{ id: 1, symbol: '螺纹钢-RB605', expiryDate: '2026-05-15', daysLeft: 15, urgency: '中等' },
{ id: 2, symbol: '原油-SC606', expiryDate: '2026-06-20', daysLeft: 30, urgency: '低' },
{ id: 3, symbol: '铜-CU604', expiryDate: '2026-04-20', daysLeft: 5, urgency: '高' },
{ id: 4, symbol: '甲醇-MA605', expiryDate: '2026-05-10', daysLeft: 10, urgency: '中等' }
];
//
const riskMetrics = [
{ name: '风险价值(VaR)', value: '12,500', unit: '元', level: '中等' },
{ name: '最大回撤', value: '8.5', unit: '%', level: '低' },
{ name: '夏普比率', value: '1.2', unit: '', level: '高' },
{ name: '仓位使用率', value: '65', unit: '%', level: '中等' }
];
//
const calculatePosition = (values) => {
const { accountBalance, riskPerTrade, stopLossPercent } = values;
const riskAmount = accountBalance * (riskPerTrade / 100);
const position = riskAmount / (stopLossPercent / 100);
setPositionSize(position);
};
//
const handleSubmit = (values) => {
calculatePosition(values);
};
//
const getUrgencyColor = (urgency) => {
if (urgency === '高') return 'red';
if (urgency === '中等') return 'orange';
return 'green';
};
//
const getRiskLevelColor = (level) => {
if (level === '高') return 'red';
if (level === '中等') return 'orange';
return 'green';
};
return (
<div className="risk-control">
<h2>风控管理</h2>
{/* 止损策略设置 */}
<Card
title={<span><SafetyOutlined /> 止损策略设置</span>}
className="risk-card"
style={{ marginBottom: 24 }}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Item label="止损策略" name="stopLossStrategy" rules={[{ required: true }]}>
<Select placeholder="选择止损策略">
{stopLossStrategies.map(strategy => (
<Option key={strategy.value} value={strategy.value}>
{strategy.label}
</Option>
))}
</Select>
</Item>
</Col>
<Col span={8}>
<Item label="止损百分比" name="stopLossPercent" rules={[{ required: true }]}>
<Input addonAfter="%" type="number" min={0.1} max={50} step={0.1} />
</Item>
</Col>
<Col span={8}>
<Item label="止损点位" name="stopLossPrice">
<Input addonBefore="¥" type="number" step={0.01} />
</Item>
</Col>
</Row>
<Button type="primary" htmlType="submit" style={{ marginTop: 8 }}>
应用止损策略
</Button>
</Form>
</Card>
{/* 仓位管理 */}
<Card
title={<span><CalculatorOutlined /> 仓位管理</span>}
className="risk-card"
style={{ marginBottom: 24 }}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Item label="账户余额" name="accountBalance" rules={[{ required: true }]}>
<Input addonBefore="¥" type="number" min={1000} step={1000} />
</Item>
</Col>
<Col span={8}>
<Item label="每笔交易风险" name="riskPerTrade" rules={[{ required: true }]}>
<Input addonAfter="%" type="number" min={0.1} max={20} step={0.1} />
</Item>
</Col>
<Col span={8}>
<Item label="止损百分比" name="stopLossPercent" rules={[{ required: true }]}>
<Input addonAfter="%" type="number" min={0.1} max={50} step={0.1} />
</Item>
</Col>
</Row>
<Button type="primary" htmlType="submit" style={{ marginBottom: 16 }}>
计算仓位
</Button>
{positionSize > 0 && (
<Card className="position-result">
<Statistic
title="建议仓位大小"
value={positionSize.toFixed(2)}
prefix="¥"
valueStyle={{ color: '#1890ff' }}
/>
<Statistic
title="风险暴露"
value={(positionSize * 0.05).toFixed(2)}
prefix="¥"
valueStyle={{ color: '#ff4d4f' }}
/>
<Statistic
title="仓位比例"
value={(positionSize / form.getFieldValue('accountBalance') * 100).toFixed(2)}
suffix="%"
valueStyle={{ color: '#52c41a' }}
/>
</Card>
)}
</Form>
{/* 风险偏好设置 */}
<Card title="风险偏好设置" style={{ marginTop: 24 }}>
<div className="risk-preference">
<div className="risk-slider">
<label>风险偏好: </label>
<Slider
min={0}
max={100}
value={riskLevel}
onChange={setRiskLevel}
marks={{
0: '保守',
50: '适中',
100: '激进'
}}
/>
<Tag color={riskLevel < 33 ? 'green' : riskLevel < 66 ? 'orange' : 'red'}>
{riskLevel < 33 ? '保守' : riskLevel < 66 ? '适中' : '激进'}
</Tag>
</div>
<Alert
message="风险提示"
description="风险偏好设置将影响系统给出的仓位建议和止损策略,请根据自身风险承受能力合理设置。"
type="info"
showIcon
style={{ marginTop: 16 }}
/>
</div>
</Card>
</Card>
{/* 换月预警 */}
<Card
title={<span><CalendarOutlined /> 换月预警</span>}
className="risk-card"
style={{ marginBottom: 24 }}
>
<Table
dataSource={rolloverAlerts}
columns={[
{
title: '合约',
dataIndex: 'symbol',
key: 'symbol'
},
{
title: '到期日',
dataIndex: 'expiryDate',
key: 'expiryDate'
},
{
title: '剩余天数',
dataIndex: 'daysLeft',
key: 'daysLeft',
render: (days) => (
<Tag color={days < 7 ? 'red' : days < 15 ? 'orange' : 'green'}>
{days}
</Tag>
)
},
{
title: '紧急程度',
dataIndex: 'urgency',
key: 'urgency',
render: (urgency) => (
<Tag color={getUrgencyColor(urgency)}>
{urgency}
</Tag>
)
},
{
title: '操作',
key: 'action',
render: () => (
<Button type="link">查看详情</Button>
)
}
]}
rowKey="id"
/>
</Card>
{/* 风险监控 */}
<Card
title={<span><BarChartOutlined /> 风险监控</span>}
className="risk-card"
>
<Row gutter={[16, 16]}>
{riskMetrics.map((metric, index) => (
<Col span={6} key={index}>
<Card className="risk-metric-card">
<Statistic
title={metric.name}
value={metric.value}
suffix={metric.unit}
valueStyle={{ color: getRiskLevelColor(metric.level) }}
/>
<div className="risk-level">
<Tag color={getRiskLevelColor(metric.level)}>
{metric.level}风险
</Tag>
</div>
</Card>
</Col>
))}
</Row>
<Alert
message="风险监控提示"
description="系统会实时监控您的交易风险,当风险指标超过预警阈值时,会及时发出预警通知。"
type="warning"
showIcon
style={{ marginTop: 16 }}
/>
</Card>
</div>
);
};
export default RiskControl;

@ -0,0 +1,123 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { generateFuturesOverview, generateFutureData, riskAlerts, aiMarketAnalysis } from '../utils/mockData';
// 模拟异步获取期货概览数据
export const fetchFuturesOverview = createAsyncThunk(
'futures/fetchOverview',
async () => {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500));
return generateFuturesOverview();
}
);
// 模拟异步获取单个期货详情
export const fetchFutureDetail = createAsyncThunk(
'futures/fetchDetail',
async ({ code, name }) => {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500));
return generateFutureData(code, name);
}
);
// 模拟异步获取风险预警
export const fetchRiskAlerts = createAsyncThunk(
'futures/fetchRiskAlerts',
async () => {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 300));
return riskAlerts;
}
);
// 模拟异步获取AI市场分析
export const fetchAIMarketAnalysis = createAsyncThunk(
'futures/fetchAIMarketAnalysis',
async () => {
// 模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 400));
return aiMarketAnalysis;
}
);
const futuresSlice = createSlice({
name: 'futures',
initialState: {
overview: [],
selectedFuture: null,
riskAlerts: [],
aiAnalysis: null,
loading: false,
error: null
},
reducers: {
selectFuture: (state, action) => {
state.selectedFuture = action.payload;
},
clearSelectedFuture: (state) => {
state.selectedFuture = null;
}
},
extraReducers: (builder) => {
builder
// 处理fetchFuturesOverview
.addCase(fetchFuturesOverview.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchFuturesOverview.fulfilled, (state, action) => {
state.loading = false;
state.overview = action.payload;
})
.addCase(fetchFuturesOverview.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
})
// 处理fetchFutureDetail
.addCase(fetchFutureDetail.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchFutureDetail.fulfilled, (state, action) => {
state.loading = false;
state.selectedFuture = action.payload;
})
.addCase(fetchFutureDetail.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
})
// 处理fetchRiskAlerts
.addCase(fetchRiskAlerts.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchRiskAlerts.fulfilled, (state, action) => {
state.loading = false;
state.riskAlerts = action.payload;
})
.addCase(fetchRiskAlerts.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
})
// 处理fetchAIMarketAnalysis
.addCase(fetchAIMarketAnalysis.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchAIMarketAnalysis.fulfilled, (state, action) => {
state.loading = false;
state.aiAnalysis = action.payload;
})
.addCase(fetchAIMarketAnalysis.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
}
});
export const { selectFuture, clearSelectedFuture } = futuresSlice.actions;
export default futuresSlice.reducer;

@ -0,0 +1,10 @@
import { configureStore } from '@reduxjs/toolkit';
import futuresReducer from './futuresSlice';
const store = configureStore({
reducer: {
futures: futuresReducer
}
});
export default store;

@ -0,0 +1,211 @@
// 模拟数据
// 期货品种列表
export const futuresList = [
// 金属类
{ code: 'AU', name: '金', type: '金属' },
{ code: 'AG', name: '银', type: '金属' },
{ code: 'CU', name: '铜', type: '金属' },
{ code: 'NI', name: '镍', type: '金属' },
{ code: 'SN', name: '锡', type: '金属' },
{ code: 'AL', name: '铝', type: '金属' },
{ code: 'ZN', name: '锌', type: '金属' },
// 建材类
{ code: 'FG', name: '玻璃', type: '建材' },
{ code: 'SJS', name: '烧碱', type: '建材' },
{ code: 'SCA', name: '纯碱', type: '建材' },
{ code: 'JM', name: '焦煤', type: '建材' },
{ code: 'RB', name: '螺纹钢', type: '建材' },
{ code: 'ALO', name: '氧化铝', type: '建材' },
// 能源化工类
{ code: 'MA', name: '甲醇', type: '能源化工' },
{ code: 'PVC', name: 'PVC', type: '能源化工' },
{ code: 'FU', name: '燃油', type: '能源化工' },
{ code: 'SC', name: '原油', type: '能源化工' },
{ code: 'L', name: '橡胶', type: '能源化工' },
{ code: 'NR', name: '20号胶', type: '能源化工' },
{ code: 'BU', name: '沥青', type: '能源化工' },
{ code: 'LU', name: '低硫燃油', type: '能源化工' },
// 农产品类
{ code: 'P', name: '棕榈油', type: '农产品' },
// 新能源类
{ code: 'LC', name: '碳酸锂', type: '新能源' },
{ code: 'SI', name: '工业硅', type: '新能源' },
{ code: 'PGS', name: '多晶硅', type: '新能源' },
// 金融类
{ code: 'IC', name: '中证500', type: '金融' },
{ code: 'IM', name: '中证1000', type: '金融' },
{ code: 'IH', name: '上证50', type: '金融' }
];
// 生成随机数据的工具函数
const generateRandomPrice = (base, volatility) => {
return +(base + (Math.random() - 0.5) * 2 * volatility).toFixed(2);
};
const generateRandomChange = () => {
return +(Math.random() * 10 - 5).toFixed(2);
};
const generateRandomWinRate = () => {
return Math.floor(Math.random() * 50) + 30;
};
const generateRandomATR = () => {
return +(Math.random() * 5 + 0.5).toFixed(2);
};
const generateRandomADX = () => {
return Math.floor(Math.random() * 60) + 10;
};
const getADXStatus = (adx) => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
};
const getTrendDirection = () => {
const directions = ['看多', '看空', '观望'];
return directions[Math.floor(Math.random() * directions.length)];
};
const getTrendStatus = (direction) => {
if (direction === '看多') return '多头趋势';
if (direction === '看空') return '空头趋势';
return '震荡';
};
const generateRandomRSI = () => {
return Math.floor(Math.random() * 80) + 10;
};
// 生成品种详细数据
export const generateFutureData = (code, name) => {
const currentPrice = generateRandomPrice(2000, 500);
const changePercent = generateRandomChange();
const atr = generateRandomATR();
const adx = generateRandomADX();
const winRate = generateRandomWinRate();
const trends = {
'5MIN': {
direction: getTrendDirection(),
status: getTrendStatus(getTrendDirection()),
rsi: generateRandomRSI()
},
'30MIN': {
direction: getTrendDirection(),
status: getTrendStatus(getTrendDirection()),
rsi: generateRandomRSI()
},
'1HOUR': {
direction: getTrendDirection(),
status: getTrendStatus(getTrendDirection()),
rsi: generateRandomRSI()
},
'1DAY': {
direction: getTrendDirection(),
status: getTrendStatus(getTrendDirection()),
rsi: generateRandomRSI()
}
};
const indicators = {
macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)],
rsi: `${generateRandomRSI()}(中性)`,
bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)],
kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)]
};
const entry = currentPrice;
const stopLoss = entry * (1 - 0.02 * (Math.random() + 0.5));
const target = entry * (1 + 0.03 * (Math.random() + 0.5));
return {
code,
name,
fullName: `${name}-${code}605`,
currentPrice,
changePercent,
atr,
adx,
adxStatus: getADXStatus(adx),
winRate,
trends,
indicators,
tradingAdvice: {
entry: +entry.toFixed(2),
stopLoss: +stopLoss.toFixed(2),
target: +target.toFixed(2)
},
riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)]
};
};
// 生成多个品种的概览数据
export const generateFuturesOverview = () => {
return futuresList.map(item => {
const data = generateFutureData(item.code, item.name);
return {
code: data.code,
name: data.name,
currentPrice: data.currentPrice,
changePercent: data.changePercent,
winRate: data.winRate,
atr: data.atr,
adx: data.adx,
adxStatus: data.adxStatus,
trends: data.trends
};
});
};
// 风险预警数据
export const riskAlerts = [
{ id: 1, title: '原油波动加剧', level: '高', message: '原油价格近期波动较大,建议控制仓位' },
{ id: 2, title: '螺纹钢换月提醒', level: '中等', message: '螺纹钢主力合约即将换月,请注意移仓' },
{ id: 3, title: '市场情绪偏空', level: '中等', message: '多数品种技术指标显示空头信号,建议谨慎操作' },
{ id: 4, title: '铜库存下降', level: '低', message: '铜库存持续下降,可能影响价格走势' }
];
// AI市场研判
export const aiMarketAnalysis = {
overallTrend: '震荡偏弱',
keyFactors: ['原油价格波动', '宏观经济数据', '政策面变化'],
recommendations: ['控制仓位', '关注原油走势', '做好止损'],
confidence: 75
};
// K线图模拟数据
export const generateKlineData = (days = 30) => {
const data = [];
let price = 2000;
for (let i = 0; i < days; i++) {
const open = price;
const high = open + Math.random() * 50;
const low = open - Math.random() * 50;
const close = low + Math.random() * (high - low);
const volume = Math.floor(Math.random() * 100000) + 10000;
data.push({
time: new Date(Date.now() - (days - i) * 24 * 60 * 60 * 1000).getTime() / 1000,
open: +open.toFixed(2),
high: +high.toFixed(2),
low: +low.toFixed(2),
close: +close.toFixed(2),
volume
});
price = close;
}
return data;
};

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
Loading…
Cancel
Save