commit
0331915728
@ -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>alphafuturespro2</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,34 @@
|
|||||||
|
{
|
||||||
|
"name": "alphafuturespro2",
|
||||||
|
"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",
|
||||||
|
"axios": "^1.13.5",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
|
"lightweight-charts": "^5.1.0",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
|
"redux": "^5.0.1"
|
||||||
|
},
|
||||||
|
"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": "^7.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,86 @@
|
|||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全局样式重置 */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式滚动条 */
|
||||||
|
[data-theme="dark"] ::-webkit-scrollbar-track {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] ::-webkit-scrollbar-thumb {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片悬停效果 */
|
||||||
|
.ant-card-hoverable:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮动画效果 */
|
||||||
|
.ant-btn {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格行悬停效果 */
|
||||||
|
.ant-table-tbody > tr:hover > td {
|
||||||
|
background-color: rgba(24, 144, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式表格行悬停效果 */
|
||||||
|
[data-theme="dark"] .ant-table-tbody > tr:hover > td {
|
||||||
|
background-color: rgba(64, 169, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
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() {
|
||||||
|
const [currentMenu, setCurrentMenu] = useState('dashboard');
|
||||||
|
|
||||||
|
// 渲染当前页面
|
||||||
|
const renderCurrentPage = () => {
|
||||||
|
switch (currentMenu) {
|
||||||
|
case 'dashboard':
|
||||||
|
return <Dashboard />;
|
||||||
|
case 'detail':
|
||||||
|
return <Detail />;
|
||||||
|
case 'risk-control':
|
||||||
|
return <RiskControl />;
|
||||||
|
case 'config':
|
||||||
|
return <Config />;
|
||||||
|
default:
|
||||||
|
return <Dashboard />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainLayout currentMenu={currentMenu} onMenuSelect={setCurrentMenu}>
|
||||||
|
{renderCurrentPage()}
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Layout, Menu, Input, Badge, Switch, Avatar } from 'antd';
|
||||||
|
import { SearchOutlined, BellOutlined, UserOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Header: AntHeader } = Layout;
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
|
const Header = ({ collapsed, onToggle, theme, onThemeChange }) => {
|
||||||
|
return (
|
||||||
|
<AntHeader style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
background: theme === 'light' ? '#fff' : '#1f1f1f',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
padding: '0 24px'
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="trigger"
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
background: 'transparent',
|
||||||
|
color: theme === 'light' ? '#262626' : '#fff'
|
||||||
|
}}
|
||||||
|
onClick={onToggle}
|
||||||
|
>
|
||||||
|
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
</button>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginLeft: '20px',
|
||||||
|
color: theme === 'light' ? '#1890ff' : '#40a9ff'
|
||||||
|
}}>
|
||||||
|
AI期货分析系统
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Search
|
||||||
|
placeholder="搜索品种"
|
||||||
|
style={{ width: 200, marginRight: 20 }}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
/>
|
||||||
|
<Badge count={3} style={{ marginRight: 20 }}>
|
||||||
|
<BellOutlined style={{ fontSize: 20, cursor: 'pointer', color: theme === 'light' ? '#262626' : '#fff' }} />
|
||||||
|
</Badge>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', marginRight: 20 }}>
|
||||||
|
<span style={{ marginRight: 8, color: theme === 'light' ? '#262626' : '#fff' }}>深色模式</span>
|
||||||
|
<Switch checked={theme === 'dark'} onChange={onThemeChange} />
|
||||||
|
</div>
|
||||||
|
<Avatar size="small" icon={<UserOutlined />} style={{ marginRight: 8 }} />
|
||||||
|
<span style={{ color: theme === 'light' ? '#262626' : '#fff' }}>管理员</span>
|
||||||
|
</div>
|
||||||
|
</AntHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
import Header from './Header';
|
||||||
|
import Sider from './Sider';
|
||||||
|
import RightPanel from './RightPanel';
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
const MainLayout = ({ children, currentMenu, onMenuSelect }) => {
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [rightCollapsed, setRightCollapsed] = useState(false);
|
||||||
|
const [theme, setTheme] = useState('light');
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRightToggle = (collapsed) => {
|
||||||
|
setRightCollapsed(collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThemeChange = (checked) => {
|
||||||
|
setTheme(checked ? 'dark' : 'light');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout style={{ minHeight: '100vh', background: theme === 'light' ? '#f0f2f5' : '#141414' }}>
|
||||||
|
<Header
|
||||||
|
collapsed={collapsed}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
theme={theme}
|
||||||
|
onThemeChange={handleThemeChange}
|
||||||
|
/>
|
||||||
|
<Layout>
|
||||||
|
<Sider
|
||||||
|
collapsed={collapsed}
|
||||||
|
theme={theme}
|
||||||
|
currentMenu={currentMenu}
|
||||||
|
onMenuSelect={onMenuSelect}
|
||||||
|
/>
|
||||||
|
<Content
|
||||||
|
style={{
|
||||||
|
margin: '24px',
|
||||||
|
padding: '24px',
|
||||||
|
background: theme === 'light' ? '#fff' : '#1f1f1f',
|
||||||
|
borderRadius: '4px',
|
||||||
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
minHeight: 280,
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Content>
|
||||||
|
<RightPanel
|
||||||
|
collapsed={rightCollapsed}
|
||||||
|
onToggle={handleRightToggle}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainLayout;
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Layout, Card, List, Badge, Collapse } from 'antd';
|
||||||
|
import { ArrowUpOutlined, ArrowDownOutlined, AlertOutlined, FireOutlined } from '@ant-design/icons';
|
||||||
|
import { marketHotspots, riskAlerts } from '../../utils/mockData';
|
||||||
|
|
||||||
|
const { Sider: AntSider } = Layout;
|
||||||
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
|
const RightPanel = ({ collapsed, onToggle, theme }) => {
|
||||||
|
const [activeTab, setActiveTab] = useState('hotspots');
|
||||||
|
|
||||||
|
// 按涨跌幅排序市场热点
|
||||||
|
const sortedHotspots = [...marketHotspots].sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent)).slice(0, 10);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AntSider
|
||||||
|
width={300}
|
||||||
|
collapsible
|
||||||
|
collapsed={collapsed}
|
||||||
|
onCollapse={onToggle}
|
||||||
|
style={{
|
||||||
|
background: theme === 'light' ? '#fff' : '#1f1f1f',
|
||||||
|
boxShadow: '-2px 0 8px rgba(0, 0, 0, 0.1)',
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
height: 64,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: theme === 'light' ? '#1890ff' : '#40a9ff',
|
||||||
|
borderBottom: `1px solid ${theme === 'light' ? '#f0f0f0' : '#333'}`
|
||||||
|
}}>
|
||||||
|
{!collapsed && '市场信息'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!collapsed && (
|
||||||
|
<div style={{ padding: '16px' }}>
|
||||||
|
<Collapse defaultActiveKey={['hotspots', 'alerts']} style={{ marginBottom: 16 }}>
|
||||||
|
<Panel
|
||||||
|
header={<div style={{ display: 'flex', alignItems: 'center' }}><FireOutlined style={{ marginRight: 8 }} />市场热点</div>}
|
||||||
|
key="hotspots"
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={sortedHotspots}
|
||||||
|
renderItem={item => (
|
||||||
|
<List.Item>
|
||||||
|
<List.Item.Meta
|
||||||
|
title={
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<span>{item.name} ({item.code})</span>
|
||||||
|
<span style={{
|
||||||
|
color: item.changePercent > 0 ? '#52c41a' : '#ff4d4f',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
{item.changePercent > 0 ? <ArrowUpOutlined style={{ fontSize: 12 }} /> : <ArrowDownOutlined style={{ fontSize: 12 }} />}
|
||||||
|
{Math.abs(item.changePercent).toFixed(2)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<div style={{ fontSize: '12px', color: theme === 'light' ? '#8c8c8c' : '#999' }}>
|
||||||
|
{item.type}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Panel
|
||||||
|
header={<div style={{ display: 'flex', alignItems: 'center' }}><AlertOutlined style={{ marginRight: 8 }} />风险预警</div>}
|
||||||
|
key="alerts"
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
dataSource={riskAlerts}
|
||||||
|
renderItem={alert => (
|
||||||
|
<List.Item>
|
||||||
|
<Badge
|
||||||
|
status={alert.level === '高' ? 'error' : alert.level === '中等' ? 'warning' : 'success'}
|
||||||
|
text={alert.level}
|
||||||
|
style={{ marginBottom: 8 }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontWeight: 'bold', marginBottom: 4 }}>{alert.title}</div>
|
||||||
|
<div style={{ fontSize: '12px', color: theme === 'light' ? '#8c8c8c' : '#999' }}>
|
||||||
|
{alert.message}
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AntSider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightPanel;
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Layout, Menu } from 'antd';
|
||||||
|
import { HomeOutlined, BarChartOutlined, AlertOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Sider: AntSider } = Layout;
|
||||||
|
|
||||||
|
const Sider = ({ collapsed, theme, currentMenu, onMenuSelect }) => {
|
||||||
|
return (
|
||||||
|
<AntSider
|
||||||
|
collapsible
|
||||||
|
collapsed={collapsed}
|
||||||
|
style={{
|
||||||
|
background: theme === 'light' ? '#fff' : '#1f1f1f',
|
||||||
|
boxShadow: '2px 0 8px rgba(0, 0, 0, 0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
height: 64,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: theme === 'light' ? '#1890ff' : '#40a9ff',
|
||||||
|
borderBottom: `1px solid ${theme === 'light' ? '#f0f0f0' : '#333'}`
|
||||||
|
}}>
|
||||||
|
{!collapsed && '功能导航'}
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
mode="inline"
|
||||||
|
selectedKeys={[currentMenu]}
|
||||||
|
onSelect={({ key }) => onMenuSelect(key)}
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
borderRight: 0,
|
||||||
|
background: theme === 'light' ? '#fff' : '#1f1f1f'
|
||||||
|
}}
|
||||||
|
theme={theme}
|
||||||
|
>
|
||||||
|
<Menu.Item key="dashboard" icon={<HomeOutlined />}>
|
||||||
|
市场概览
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="detail" icon={<BarChartOutlined />}>
|
||||||
|
详情分析
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="risk-control" icon={<AlertOutlined />}>
|
||||||
|
风控管理
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="config" icon={<SettingOutlined />}>
|
||||||
|
配置管理
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
</AntSider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sider;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useTheme = () => {
|
||||||
|
const [theme, setTheme] = useState('light');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 从localStorage读取主题设置
|
||||||
|
const savedTheme = localStorage.getItem('theme');
|
||||||
|
if (savedTheme) {
|
||||||
|
setTheme(savedTheme);
|
||||||
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const newTheme = theme === 'light' ? 'dark' : 'light';
|
||||||
|
setTheme(newTheme);
|
||||||
|
localStorage.setItem('theme', newTheme);
|
||||||
|
document.documentElement.setAttribute('data-theme', newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { theme, toggleTheme };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTheme;
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
:root {
|
||||||
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式基础样式 */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
body {
|
||||||
|
background-color: #141414;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保所有元素继承box-sizing */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除默认列表样式 */
|
||||||
|
ul, ol {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除默认链接样式 */
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保图片响应式 */
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保表单元素继承字体 */
|
||||||
|
input, button, select, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保按钮和输入框的默认样式一致 */
|
||||||
|
button, input[type="submit"], input[type="reset"] {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 清除浮动 */
|
||||||
|
.clearfix::after {
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.jsx'
|
||||||
|
import 'antd/dist/reset.css'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@ -0,0 +1,357 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Card, Row, Col, Form, Input, Select, Switch, Slider, Button, Table, Badge, Typography, Tabs } from 'antd';
|
||||||
|
import { SaveOutlined, ReloadOutlined, DatabaseOutlined, ToolOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
import useTheme from '../../hooks/useTheme';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
const { Column } = Table;
|
||||||
|
|
||||||
|
const Config = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [activeTab, setActiveTab] = useState('data-source');
|
||||||
|
|
||||||
|
// 数据源配置
|
||||||
|
const dataSources = [
|
||||||
|
{ id: 1, name: 'CTP行情', type: '期货', status: '在线', responseTime: '23ms', priority: 1, enabled: true },
|
||||||
|
{ id: 2, name: 'Wind资讯', type: '综合', status: '在线', responseTime: '45ms', priority: 2, enabled: true },
|
||||||
|
{ id: 3, name: '东方财富', type: '股票', status: '在线', responseTime: '67ms', priority: 3, enabled: false },
|
||||||
|
{ id: 4, name: '同花顺', type: '股票', status: '离线', responseTime: '-', priority: 4, enabled: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
// AI模型配置
|
||||||
|
const aiModels = [
|
||||||
|
{ id: 1, name: 'LSTM预测模型', type: '时间序列', accuracy: '85%', responseTime: '120ms', enabled: true },
|
||||||
|
{ id: 2, name: 'XGBoost分类模型', type: '分类', accuracy: '82%', responseTime: '80ms', enabled: true },
|
||||||
|
{ id: 3, name: 'Transformer模型', type: '时间序列', accuracy: '88%', responseTime: '200ms', enabled: false },
|
||||||
|
{ id: 4, name: '随机森林模型', type: '分类', accuracy: '78%', responseTime: '60ms', enabled: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const onFinish = (values) => {
|
||||||
|
console.log('配置设置:', values);
|
||||||
|
// 这里可以添加保存逻辑
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换数据源状态
|
||||||
|
const toggleDataSource = (id) => {
|
||||||
|
console.log('切换数据源状态:', id);
|
||||||
|
// 这里可以添加切换逻辑
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换AI模型状态
|
||||||
|
const toggleAiModel = (id) => {
|
||||||
|
console.log('切换AI模型状态:', id);
|
||||||
|
// 这里可以添加切换逻辑
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* 页面头部 */}
|
||||||
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
|
<Col flex="auto">
|
||||||
|
<Title level={3}>配置管理</Title>
|
||||||
|
<Text>管理数据源、AI模型、系统参数和用户偏好</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Tabs activeKey={activeTab} onChange={setActiveTab} style={{ marginBottom: 24 }}>
|
||||||
|
{/* 数据源配置 */}
|
||||||
|
<TabPane tab={<div style={{ display: 'flex', alignItems: 'center' }}><DatabaseOutlined style={{ marginRight: 8 }} />数据源配置</div>} key="data-source">
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Table dataSource={dataSources} rowKey="id" size="small">
|
||||||
|
<Column title="数据源名称" dataIndex="name" />
|
||||||
|
<Column title="类型" dataIndex="type" />
|
||||||
|
<Column
|
||||||
|
title="状态"
|
||||||
|
dataIndex="status"
|
||||||
|
render={(text) => (
|
||||||
|
<Badge status={text === '在线' ? 'success' : 'error'} text={text} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column title="响应时间" dataIndex="responseTime" />
|
||||||
|
<Column title="优先级" dataIndex="priority" />
|
||||||
|
<Column
|
||||||
|
title="启用"
|
||||||
|
dataIndex="enabled"
|
||||||
|
render={(text, record) => (
|
||||||
|
<Switch checked={text} onChange={() => toggleDataSource(record.id)} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
render={() => (
|
||||||
|
<Button size="small" icon={<ReloadOutlined />}>刷新</Button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="添加数据源">
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="数据源名称"
|
||||||
|
name="dataSourceName"
|
||||||
|
rules={[{ required: true, message: '请输入数据源名称' }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="类型"
|
||||||
|
name="dataSourceType"
|
||||||
|
rules={[{ required: true, message: '请选择数据源类型' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择数据源类型">
|
||||||
|
<Option value="期货">期货</Option>
|
||||||
|
<Option value="股票">股票</Option>
|
||||||
|
<Option value="综合">综合</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="优先级"
|
||||||
|
name="dataSourcePriority"
|
||||||
|
rules={[{ required: true, message: '请输入优先级' }]}
|
||||||
|
>
|
||||||
|
<Input type="number" min={1} max={10} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="启用"
|
||||||
|
name="dataSourceEnabled"
|
||||||
|
>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} style={{ marginRight: 8 }}>
|
||||||
|
添加数据源
|
||||||
|
</Button>
|
||||||
|
<Button>取消</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
{/* AI模型配置 */}
|
||||||
|
<TabPane tab={<div style={{ display: 'flex', alignItems: 'center' }}><ToolOutlined style={{ marginRight: 8 }} />AI模型配置</div>} key="ai-model">
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Table dataSource={aiModels} rowKey="id" size="small">
|
||||||
|
<Column title="模型名称" dataIndex="name" />
|
||||||
|
<Column title="类型" dataIndex="type" />
|
||||||
|
<Column title="准确率" dataIndex="accuracy" />
|
||||||
|
<Column title="响应时间" dataIndex="responseTime" />
|
||||||
|
<Column
|
||||||
|
title="启用"
|
||||||
|
dataIndex="enabled"
|
||||||
|
render={(text, record) => (
|
||||||
|
<Switch checked={text} onChange={() => toggleAiModel(record.id)} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
render={() => (
|
||||||
|
<Button size="small">配置</Button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card title="模型参数调优">
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Text strong>学习率</Text>
|
||||||
|
<Slider defaultValue={0.01} min={0.001} max={0.1} step={0.001} />
|
||||||
|
<Text style={{ display: 'block', marginTop: 8 }}>当前值: 0.01</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Text strong>批次大小</Text>
|
||||||
|
<Slider defaultValue={32} min={8} max={128} step={8} />
|
||||||
|
<Text style={{ display: 'block', marginTop: 8 }}>当前值: 32</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Text strong>训练轮数</Text>
|
||||||
|
<Slider defaultValue={100} min={10} max={500} step={10} />
|
||||||
|
<Text style={{ display: 'block', marginTop: 8 }}>当前值: 100</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Text strong>特征维度</Text>
|
||||||
|
<Slider defaultValue={64} min={16} max={256} step={16} />
|
||||||
|
<Text style={{ display: 'block', marginTop: 8 }}>当前值: 64</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Button type="primary" icon={<SaveOutlined />} style={{ marginRight: 8 }}>
|
||||||
|
保存参数
|
||||||
|
</Button>
|
||||||
|
<Button>重置默认</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
{/* 系统配置 */}
|
||||||
|
<TabPane tab={<div style={{ display: 'flex', alignItems: 'center' }}><SettingOutlined style={{ marginRight: 8 }} />系统配置</div>} key="system">
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="分析周期"
|
||||||
|
name="analysisPeriod"
|
||||||
|
rules={[{ required: true, message: '请选择分析周期' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择分析周期">
|
||||||
|
<Option value="1MIN">1分钟</Option>
|
||||||
|
<Option value="5MIN">5分钟</Option>
|
||||||
|
<Option value="15MIN">15分钟</Option>
|
||||||
|
<Option value="30MIN">30分钟</Option>
|
||||||
|
<Option value="1HOUR">1小时</Option>
|
||||||
|
<Option value="1DAY">1天</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="数据刷新间隔"
|
||||||
|
name="refreshInterval"
|
||||||
|
rules={[{ required: true, message: '请输入数据刷新间隔' }]}
|
||||||
|
>
|
||||||
|
<Input addonAfter="秒" type="number" min={1} max={300} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="风险预警阈值"
|
||||||
|
name="riskAlertThreshold"
|
||||||
|
rules={[{ required: true, message: '请输入风险预警阈值' }]}
|
||||||
|
>
|
||||||
|
<Input addonAfter="%" type="number" min={0.1} max={10} step={0.1} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="最大并发请求数"
|
||||||
|
name="maxConcurrentRequests"
|
||||||
|
rules={[{ required: true, message: '请输入最大并发请求数' }]}
|
||||||
|
>
|
||||||
|
<Input type="number" min={1} max={100} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} style={{ marginRight: 8 }}>
|
||||||
|
保存配置
|
||||||
|
</Button>
|
||||||
|
<Button>重置默认</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
{/* 用户偏好 */}
|
||||||
|
<TabPane tab={<div style={{ display: 'flex', alignItems: 'center' }}><UserOutlined style={{ marginRight: 8 }} />用户偏好</div>} key="user-preference">
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="默认页面"
|
||||||
|
name="defaultPage"
|
||||||
|
rules={[{ required: true, message: '请选择默认页面' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择默认页面">
|
||||||
|
<Option value="dashboard">市场概览</Option>
|
||||||
|
<Option value="detail">详情分析</Option>
|
||||||
|
<Option value="risk-control">风控管理</Option>
|
||||||
|
<Option value="config">配置管理</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="默认品种"
|
||||||
|
name="defaultFuture"
|
||||||
|
rules={[{ required: true, message: '请选择默认品种' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择默认品种">
|
||||||
|
<Option value="MA">甲醇</Option>
|
||||||
|
<Option value="CU">铜</Option>
|
||||||
|
<Option value="SC">原油</Option>
|
||||||
|
<Option value="RB">螺纹钢</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Text strong>通知设置</Text>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="风险预警通知"
|
||||||
|
name="riskAlertNotification"
|
||||||
|
>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="行情异动通知"
|
||||||
|
name="marketMovementNotification"
|
||||||
|
>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="AI模型更新通知"
|
||||||
|
name="aiModelUpdateNotification"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="系统消息通知"
|
||||||
|
name="systemMessageNotification"
|
||||||
|
>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} style={{ marginRight: 8 }}>
|
||||||
|
保存偏好
|
||||||
|
</Button>
|
||||||
|
<Button>重置默认</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Config;
|
||||||
@ -0,0 +1,268 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Card, Row, Col, Form, Input, Select, Slider, Button, Table, Badge, Statistic, Typography, Alert } from 'antd';
|
||||||
|
import { SaveOutlined, CalculatorOutlined, CalendarOutlined, AlertOutlined } from '@ant-design/icons';
|
||||||
|
import useTheme from '../../hooks/useTheme';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
const { Column } = Table;
|
||||||
|
|
||||||
|
const RiskControl = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [positionSize, setPositionSize] = useState(0);
|
||||||
|
const [riskLevel, setRiskLevel] = useState('中等');
|
||||||
|
|
||||||
|
// 止损策略选项
|
||||||
|
const stopLossStrategies = [
|
||||||
|
{ value: 'fixed', label: '固定止损' },
|
||||||
|
{ value: 'trailing', label: '移动止损' },
|
||||||
|
{ value: 'volatility', label: '波动率止损' },
|
||||||
|
{ value: 'support', label: '支撑位止损' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 换月预警数据
|
||||||
|
const rolloverAlerts = [
|
||||||
|
{ id: 1, code: 'MA', name: '甲醇', currentContract: 'MA605', nextContract: 'MA606', expiryDate: '2024-05-15', daysLeft: 15 },
|
||||||
|
{ id: 2, code: 'CU', name: '铜', currentContract: 'CU605', nextContract: 'CU606', expiryDate: '2024-05-20', daysLeft: 20 },
|
||||||
|
{ id: 3, code: 'SC', name: '原油', currentContract: 'SC605', nextContract: 'SC606', expiryDate: '2024-05-18', daysLeft: 18 },
|
||||||
|
{ id: 4, code: 'RB', name: '螺纹钢', currentContract: 'RB605', nextContract: 'RB606', expiryDate: '2024-05-10', daysLeft: 10 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 风险监控数据
|
||||||
|
const riskMetrics = [
|
||||||
|
{ id: 1, name: '风险价值 (VaR)', value: '12,500', unit: '元', threshold: '20,000', status: 'normal' },
|
||||||
|
{ id: 2, name: '最大回撤', value: '8.5', unit: '%', threshold: '15', status: 'normal' },
|
||||||
|
{ id: 3, name: '夏普比率', value: '1.2', unit: '', threshold: '0.8', status: 'normal' },
|
||||||
|
{ id: 4, name: '仓位使用率', value: '65', unit: '%', threshold: '80', status: 'normal' },
|
||||||
|
{ id: 5, name: '杠杆率', value: '2.5', unit: '倍', threshold: '3', status: 'normal' },
|
||||||
|
{ id: 6, name: '日均波动', value: '2.3', unit: '%', threshold: '3', status: 'normal' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 计算仓位
|
||||||
|
const calculatePosition = (values) => {
|
||||||
|
const { totalCapital, riskPercentage, entryPrice, stopLossPrice } = values;
|
||||||
|
const riskPerTrade = totalCapital * (riskPercentage / 100);
|
||||||
|
const priceDiff = Math.abs(entryPrice - stopLossPrice);
|
||||||
|
const position = priceDiff > 0 ? riskPerTrade / priceDiff : 0;
|
||||||
|
setPositionSize(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const onFinish = (values) => {
|
||||||
|
console.log('风控设置:', values);
|
||||||
|
// 这里可以添加保存逻辑
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* 页面头部 */}
|
||||||
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||||
|
<Col flex="auto">
|
||||||
|
<Title level={3}>风控管理</Title>
|
||||||
|
<Text>设置止损策略、管理仓位、监控风险</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* 风险概览 */}
|
||||||
|
<Card style={{ marginBottom: 24 }}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<Statistic
|
||||||
|
title="风险等级"
|
||||||
|
value={riskLevel}
|
||||||
|
valueStyle={{ color: riskLevel === '高' ? '#ff4d4f' : riskLevel === '中等' ? '#faad14' : '#52c41a' }}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<Statistic title="总资金" value={1000000} suffix="元" />
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<Statistic title="已用资金" value={650000} suffix="元" />
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6}>
|
||||||
|
<Statistic title="可用资金" value={350000} suffix="元" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 止损策略区 */}
|
||||||
|
<Card title="止损策略设置" style={{ marginBottom: 24 }}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="止损策略"
|
||||||
|
name="stopLossStrategy"
|
||||||
|
rules={[{ required: true, message: '请选择止损策略' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择止损策略">
|
||||||
|
{stopLossStrategies.map(strategy => (
|
||||||
|
<Option key={strategy.value} value={strategy.value}>{strategy.label}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="止损比例"
|
||||||
|
name="stopLossPercentage"
|
||||||
|
rules={[{ required: true, message: '请输入止损比例' }]}
|
||||||
|
>
|
||||||
|
<Input addonAfter="%" type="number" min={0.1} max={10} step={0.1} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="止损价格"
|
||||||
|
name="stopLossPrice"
|
||||||
|
rules={[{ required: true, message: '请输入止损价格' }]}
|
||||||
|
>
|
||||||
|
<Input addonAfter="元" type="number" step={0.01} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="移动止损距离"
|
||||||
|
name="trailingDistance"
|
||||||
|
>
|
||||||
|
<Input addonAfter="元" type="number" step={0.01} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" icon={<SaveOutlined />} style={{ marginRight: 8 }}>
|
||||||
|
保存设置
|
||||||
|
</Button>
|
||||||
|
<Button>重置</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 仓位管理区 */}
|
||||||
|
<Card title="仓位计算器" style={{ marginBottom: 24 }}>
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
onValuesChange={calculatePosition}
|
||||||
|
>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="总资金"
|
||||||
|
name="totalCapital"
|
||||||
|
initialValue={1000000}
|
||||||
|
>
|
||||||
|
<Input addonAfter="元" type="number" min={1000} step={1000} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="每笔风险比例"
|
||||||
|
name="riskPercentage"
|
||||||
|
initialValue={2}
|
||||||
|
>
|
||||||
|
<Input addonAfter="%" type="number" min={0.1} max={10} step={0.1} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="入场价格"
|
||||||
|
name="entryPrice"
|
||||||
|
initialValue={2000}
|
||||||
|
>
|
||||||
|
<Input addonAfter="元" type="number" step={0.01} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} md={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="止损价格"
|
||||||
|
name="stopLossPrice"
|
||||||
|
initialValue={1960}
|
||||||
|
>
|
||||||
|
<Input addonAfter="元" type="number" step={0.01} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24}>
|
||||||
|
<Card style={{ marginBottom: 16 }}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic title="建议仓位" value={positionSize.toFixed(2)} suffix="手" />
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic title="风险金额" value={(1000000 * 0.02).toFixed(2)} suffix="元" />
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic title="仓位比例" value={(positionSize * 2000 / 1000000 * 100).toFixed(2)} suffix="%" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 换月预警区 */}
|
||||||
|
<Card title="换月预警" icon={<CalendarOutlined />} style={{ marginBottom: 24 }}>
|
||||||
|
<Table dataSource={rolloverAlerts} rowKey="id" size="small">
|
||||||
|
<Column title="品种" dataIndex="name" render={(text, record) => `${text} (${record.code})`} />
|
||||||
|
<Column title="当前合约" dataIndex="currentContract" />
|
||||||
|
<Column title="下一合约" dataIndex="nextContract" />
|
||||||
|
<Column title="到期日" dataIndex="expiryDate" />
|
||||||
|
<Column
|
||||||
|
title="剩余天数"
|
||||||
|
dataIndex="daysLeft"
|
||||||
|
render={(text) => (
|
||||||
|
<Badge status={text <= 5 ? 'error' : text <= 10 ? 'warning' : 'success'} text={text} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
render={() => (
|
||||||
|
<Button size="small">查看详情</Button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 风险监控区 */}
|
||||||
|
<Card title="风险监控" icon={<AlertOutlined />} style={{ marginBottom: 24 }}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{riskMetrics.map(metric => (
|
||||||
|
<Col key={metric.id} xs={24} sm={12} md={8} lg={4}>
|
||||||
|
<Card size="small" style={{ height: '100%' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||||
|
<Text>{metric.name}</Text>
|
||||||
|
<Badge status={metric.status === 'normal' ? 'success' : 'error'} text={metric.status === 'normal' ? '正常' : '超标'} />
|
||||||
|
</div>
|
||||||
|
<Statistic
|
||||||
|
value={metric.value}
|
||||||
|
suffix={metric.unit}
|
||||||
|
size="small"
|
||||||
|
valueStyle={{ color: metric.status === 'normal' ? '#52c41a' : '#ff4d4f' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: '12px', color: '#8c8c8c', marginTop: 4 }}>
|
||||||
|
阈值: {metric.threshold}{metric.unit}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 风险提示 */}
|
||||||
|
<Alert
|
||||||
|
message="风险提示"
|
||||||
|
description="请根据自身风险承受能力设置合理的风控参数,避免过度交易和重仓操作。"
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RiskControl;
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
// 期货品种列表
|
||||||
|
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: '金融' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 生成模拟品种数据
|
||||||
|
export const generateFutureData = (code, name) => {
|
||||||
|
const currentPrice = Math.random() * 10000 + 1000;
|
||||||
|
const changePercent = (Math.random() * 10 - 5).toFixed(2);
|
||||||
|
const atr = (Math.random() * 10).toFixed(2);
|
||||||
|
const adx = Math.floor(Math.random() * 100);
|
||||||
|
const winRate = Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
|
let adxStatus = '无趋势/震荡';
|
||||||
|
if (adx > 40) {
|
||||||
|
adxStatus = '强趋势';
|
||||||
|
} else if (adx > 25) {
|
||||||
|
adxStatus = '弱趋势';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
fullName: `${name}-${code}605`,
|
||||||
|
currentPrice: currentPrice.toFixed(2),
|
||||||
|
changePercent: parseFloat(changePercent),
|
||||||
|
atr: parseFloat(atr),
|
||||||
|
adx,
|
||||||
|
adxStatus,
|
||||||
|
winRate,
|
||||||
|
trends: {
|
||||||
|
'5MIN': { direction: Math.random() > 0.5 ? '看多' : '看空', status: Math.random() > 0.5 ? '多头趋势' : '空头趋势', rsi: Math.floor(Math.random() * 100) },
|
||||||
|
'30MIN': { direction: Math.random() > 0.5 ? '看多' : '看空', status: Math.random() > 0.5 ? '多头趋势' : '空头趋势', rsi: Math.floor(Math.random() * 100) },
|
||||||
|
'1HOUR': { direction: Math.random() > 0.5 ? '看多' : '看空', status: Math.random() > 0.5 ? '多头趋势' : '空头趋势', rsi: Math.floor(Math.random() * 100) },
|
||||||
|
'1DAY': { direction: Math.random() > 0.5 ? '看多' : '看空', status: Math.random() > 0.5 ? '多头趋势' : '空头趋势', rsi: Math.floor(Math.random() * 100) }
|
||||||
|
},
|
||||||
|
indicators: {
|
||||||
|
macd: Math.random() > 0.5 ? '金叉向上' : '死叉向下',
|
||||||
|
rsi: `${Math.floor(Math.random() * 100)}(中性)`,
|
||||||
|
bollinger: ['触及上轨', '触及下轨', '在轨道内'][Math.floor(Math.random() * 3)],
|
||||||
|
kdj: Math.random() > 0.5 ? '金叉向上' : '死叉向下'
|
||||||
|
},
|
||||||
|
tradingAdvice: {
|
||||||
|
entry: currentPrice.toFixed(2),
|
||||||
|
stopLoss: (currentPrice * (1 - 0.02)).toFixed(2),
|
||||||
|
target: (currentPrice * (1 + 0.03)).toFixed(2)
|
||||||
|
},
|
||||||
|
riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
|
||||||
|
volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成所有品种的模拟数据
|
||||||
|
export const allFuturesData = futuresList.map(item => generateFutureData(item.code, item.name));
|
||||||
|
|
||||||
|
// 市场热点数据
|
||||||
|
export const marketHotspots = allFuturesData.map(item => ({
|
||||||
|
code: item.code,
|
||||||
|
name: item.name,
|
||||||
|
changePercent: item.changePercent,
|
||||||
|
type: futuresList.find(f => f.code === item.code).type
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 风险预警数据
|
||||||
|
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 aiAnalysis = {
|
||||||
|
overallView: '当前市场整体处于震荡格局,能源化工板块表现较弱,金属板块相对强势。',
|
||||||
|
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() * 100;
|
||||||
|
const low = open - Math.random() * 100;
|
||||||
|
const close = low + Math.random() * (high - low);
|
||||||
|
const volume = Math.random() * 1000000;
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
time: new Date(Date.now() - (days - i) * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
|
||||||
|
open: open.toFixed(2),
|
||||||
|
high: high.toFixed(2),
|
||||||
|
low: low.toFixed(2),
|
||||||
|
close: close.toFixed(2),
|
||||||
|
volume: volume.toFixed(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
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…
Reference in new issue