feat: 添加二级导航,解决二级导航的展示问题
This commit is contained in:
@@ -38,6 +38,139 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { useAuthModal } from '../../contexts/AuthModalContext';
|
import { useAuthModal } from '../../contexts/AuthModalContext';
|
||||||
|
|
||||||
|
/** 二级导航栏组件 - 显示当前一级菜单下的所有二级菜单项 */
|
||||||
|
const SecondaryNav = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const navbarBg = useColorModeValue('gray.50', 'gray.700');
|
||||||
|
const itemHoverBg = useColorModeValue('white', 'gray.600');
|
||||||
|
|
||||||
|
// 定义二级导航结构
|
||||||
|
const secondaryNavConfig = {
|
||||||
|
'/community': {
|
||||||
|
title: '高频跟踪',
|
||||||
|
items: [
|
||||||
|
{ path: '/community', label: '新闻催化分析', badges: [{ text: 'HOT', colorScheme: 'green' }, { text: 'NEW', colorScheme: 'red' }] },
|
||||||
|
{ path: '/concepts', label: '概念中心', badges: [{ text: 'NEW', colorScheme: 'red' }] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/concepts': {
|
||||||
|
title: '高频跟踪',
|
||||||
|
items: [
|
||||||
|
{ path: '/community', label: '新闻催化分析', badges: [{ text: 'HOT', colorScheme: 'green' }, { text: 'NEW', colorScheme: 'red' }] },
|
||||||
|
{ path: '/concepts', label: '概念中心', badges: [{ text: 'NEW', colorScheme: 'red' }] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/limit-analyse': {
|
||||||
|
title: '行情复盘',
|
||||||
|
items: [
|
||||||
|
{ path: '/limit-analyse', label: '涨停分析', badges: [{ text: 'FREE', colorScheme: 'blue' }] },
|
||||||
|
{ path: '/stocks', label: '个股中心', badges: [{ text: 'HOT', colorScheme: 'green' }] },
|
||||||
|
{ path: '/trading-simulation', label: '模拟盘', badges: [{ text: 'NEW', colorScheme: 'red' }] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'/stocks': {
|
||||||
|
title: '行情复盘',
|
||||||
|
items: [
|
||||||
|
{ path: '/limit-analyse', label: '涨停分析', badges: [{ text: 'FREE', colorScheme: 'blue' }] },
|
||||||
|
{ path: '/stocks', label: '个股中心', badges: [{ text: 'HOT', colorScheme: 'green' }] },
|
||||||
|
{ path: '/trading-simulation', label: '模拟盘', badges: [{ text: 'NEW', colorScheme: 'red' }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 找到当前路径对应的二级导航配置
|
||||||
|
const currentConfig = Object.keys(secondaryNavConfig).find(key =>
|
||||||
|
location.pathname.includes(key)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果没有匹配的二级导航,不显示
|
||||||
|
if (!currentConfig) return null;
|
||||||
|
|
||||||
|
const config = secondaryNavConfig[currentConfig];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg={navbarBg}
|
||||||
|
borderBottom="1px"
|
||||||
|
borderColor={useColorModeValue('gray.200', 'gray.600')}
|
||||||
|
py={2}
|
||||||
|
position="sticky"
|
||||||
|
top="60px"
|
||||||
|
zIndex={100}
|
||||||
|
>
|
||||||
|
<Container maxW="container.xl" px={4}>
|
||||||
|
<HStack spacing={1}>
|
||||||
|
{/* 显示一级菜单标题 */}
|
||||||
|
<Text fontSize="sm" color="gray.500" mr={2}>
|
||||||
|
{config.title}:
|
||||||
|
</Text>
|
||||||
|
{/* 二级菜单项 */}
|
||||||
|
{config.items.map((item, index) => {
|
||||||
|
const isActive = location.pathname.includes(item.path);
|
||||||
|
return item.external ? (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
as="a"
|
||||||
|
href={item.path}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
bg="transparent"
|
||||||
|
color="inherit"
|
||||||
|
fontWeight="normal"
|
||||||
|
_hover={{ bg: itemHoverBg }}
|
||||||
|
borderRadius="md"
|
||||||
|
px={3}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap={2}>
|
||||||
|
<Text>{item.label}</Text>
|
||||||
|
{item.badges && item.badges.length > 0 && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
{item.badges.map((badge, bIndex) => (
|
||||||
|
<Badge key={bIndex} size="xs" colorScheme={badge.colorScheme}>
|
||||||
|
{badge.text}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
onClick={() => navigate(item.path)}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
bg={isActive ? 'blue.50' : 'transparent'}
|
||||||
|
color={isActive ? 'blue.600' : 'inherit'}
|
||||||
|
fontWeight={isActive ? 'bold' : 'normal'}
|
||||||
|
borderBottom={isActive ? '2px solid' : 'none'}
|
||||||
|
borderColor="blue.600"
|
||||||
|
borderRadius={isActive ? '0' : 'md'}
|
||||||
|
_hover={{ bg: isActive ? 'blue.100' : itemHoverBg }}
|
||||||
|
px={3}
|
||||||
|
>
|
||||||
|
<Flex align="center" gap={2}>
|
||||||
|
<Text>{item.label}</Text>
|
||||||
|
{item.badges && item.badges.length > 0 && (
|
||||||
|
<HStack spacing={1}>
|
||||||
|
{item.badges.map((badge, bIndex) => (
|
||||||
|
<Badge key={bIndex} size="xs" colorScheme={badge.colorScheme}>
|
||||||
|
{badge.text}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</HStack>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/** 桌面端导航 - 完全按照原网站
|
/** 桌面端导航 - 完全按照原网站
|
||||||
* @TODO 添加逻辑 不展示导航case
|
* @TODO 添加逻辑 不展示导航case
|
||||||
* 1.未登陆状态 && 是首页
|
* 1.未登陆状态 && 是首页
|
||||||
@@ -163,12 +296,16 @@ const NavItems = ({ isAuthenticated, user }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://valuefrontier.cn/trading-simulation"
|
onClick={() => navigate('/trading-simulation')}
|
||||||
isExternal
|
|
||||||
py={2}
|
py={2}
|
||||||
px={3}
|
px={3}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
_hover={{ bg: 'gray.100' }}
|
_hover={{ bg: 'gray.100' }}
|
||||||
|
cursor="pointer"
|
||||||
|
bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'}
|
||||||
|
borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'}
|
||||||
|
borderColor="blue.600"
|
||||||
|
fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Text fontSize="sm">模拟盘</Text>
|
<Text fontSize="sm">模拟盘</Text>
|
||||||
@@ -978,12 +1115,19 @@ export default function HomeNavbar() {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/trading-simulation"
|
onClick={() => {
|
||||||
isExternal
|
navigate('/trading-simulation');
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
py={1}
|
py={1}
|
||||||
px={3}
|
px={3}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
_hover={{ bg: 'gray.100' }}
|
_hover={{ bg: 'gray.100' }}
|
||||||
|
cursor="pointer"
|
||||||
|
bg={location.pathname.includes('/trading-simulation') ? 'blue.50' : 'transparent'}
|
||||||
|
borderLeft={location.pathname.includes('/trading-simulation') ? '3px solid' : 'none'}
|
||||||
|
borderColor="blue.600"
|
||||||
|
fontWeight={location.pathname.includes('/trading-simulation') ? 'bold' : 'normal'}
|
||||||
>
|
>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text fontSize="sm">模拟盘</Text>
|
<Text fontSize="sm">模拟盘</Text>
|
||||||
@@ -1058,6 +1202,9 @@ export default function HomeNavbar() {
|
|||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* 二级导航栏 - 显示当前页面所属的二级菜单 */}
|
||||||
|
{!isMobile && <SecondaryNav />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,11 @@ import SettingsPage from "views/Settings/SettingsPage";
|
|||||||
import CenterDashboard from "views/Dashboard/Center";
|
import CenterDashboard from "views/Dashboard/Center";
|
||||||
import Subscription from "views/Pages/Account/Subscription";
|
import Subscription from "views/Pages/Account/Subscription";
|
||||||
|
|
||||||
// 懒加载隐私政策、用户协议和微信回调页面
|
// 懒加载隐私政策、用户协议、微信回调和模拟交易页面
|
||||||
const PrivacyPolicy = React.lazy(() => import("views/Pages/PrivacyPolicy"));
|
const PrivacyPolicy = React.lazy(() => import("views/Pages/PrivacyPolicy"));
|
||||||
const UserAgreement = React.lazy(() => import("views/Pages/UserAgreement"));
|
const UserAgreement = React.lazy(() => import("views/Pages/UserAgreement"));
|
||||||
const WechatCallback = React.lazy(() => import("views/Pages/WechatCallback"));
|
const WechatCallback = React.lazy(() => import("views/Pages/WechatCallback"));
|
||||||
|
const TradingSimulation = React.lazy(() => import("views/TradingSimulation"));
|
||||||
|
|
||||||
// 导入保护路由组件
|
// 导入保护路由组件
|
||||||
import ProtectedRoute from "../components/ProtectedRoute";
|
import ProtectedRoute from "../components/ProtectedRoute";
|
||||||
@@ -71,6 +72,16 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 模拟盘交易页面 */}
|
||||||
|
<Route
|
||||||
|
path="/trading-simulation"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<TradingSimulation />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 隐私政策页面 - 无需登录 */}
|
{/* 隐私政策页面 - 无需登录 */}
|
||||||
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
|
<Route path="/privacy-policy" element={<PrivacyPolicy />} />
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import { authHandlers } from './auth';
|
import { authHandlers } from './auth';
|
||||||
import { accountHandlers } from './account';
|
import { accountHandlers } from './account';
|
||||||
|
import { simulationHandlers } from './simulation';
|
||||||
|
|
||||||
// 可以在这里添加更多的 handlers
|
// 可以在这里添加更多的 handlers
|
||||||
// import { userHandlers } from './user';
|
// import { userHandlers } from './user';
|
||||||
@@ -11,6 +12,7 @@ import { accountHandlers } from './account';
|
|||||||
export const handlers = [
|
export const handlers = [
|
||||||
...authHandlers,
|
...authHandlers,
|
||||||
...accountHandlers,
|
...accountHandlers,
|
||||||
|
...simulationHandlers,
|
||||||
// ...userHandlers,
|
// ...userHandlers,
|
||||||
// ...eventHandlers,
|
// ...eventHandlers,
|
||||||
];
|
];
|
||||||
|
|||||||
374
src/mocks/handlers/simulation.js
Normal file
374
src/mocks/handlers/simulation.js
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
// src/mocks/handlers/simulation.js
|
||||||
|
import { http, HttpResponse, delay } from 'msw';
|
||||||
|
import { getCurrentUser } from '../data/users';
|
||||||
|
|
||||||
|
// 模拟网络延迟(毫秒)
|
||||||
|
const NETWORK_DELAY = 300;
|
||||||
|
|
||||||
|
// 模拟交易账户数据
|
||||||
|
let mockTradingAccount = {
|
||||||
|
account_id: 'sim_001',
|
||||||
|
account_name: '模拟交易账户',
|
||||||
|
initial_capital: 1000000,
|
||||||
|
available_cash: 850000,
|
||||||
|
frozen_cash: 0,
|
||||||
|
position_value: 150000,
|
||||||
|
total_assets: 1000000,
|
||||||
|
total_profit: 0,
|
||||||
|
total_profit_rate: 0,
|
||||||
|
daily_profit: 0,
|
||||||
|
daily_profit_rate: 0,
|
||||||
|
created_at: '2024-01-01T00:00:00Z',
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟持仓数据
|
||||||
|
let mockPositions = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
stock_code: '600036',
|
||||||
|
stock_name: '招商银行',
|
||||||
|
position_qty: 1000,
|
||||||
|
available_qty: 1000,
|
||||||
|
frozen_qty: 0,
|
||||||
|
avg_cost: 42.50,
|
||||||
|
current_price: 42.80,
|
||||||
|
market_value: 42800,
|
||||||
|
profit: 300,
|
||||||
|
profit_rate: 0.71,
|
||||||
|
today_profit: 100,
|
||||||
|
today_profit_rate: 0.23,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
stock_code: '000001',
|
||||||
|
stock_name: '平安银行',
|
||||||
|
position_qty: 2000,
|
||||||
|
available_qty: 2000,
|
||||||
|
frozen_qty: 0,
|
||||||
|
avg_cost: 12.30,
|
||||||
|
current_price: 12.50,
|
||||||
|
market_value: 25000,
|
||||||
|
profit: 400,
|
||||||
|
profit_rate: 1.63,
|
||||||
|
today_profit: -50,
|
||||||
|
today_profit_rate: -0.20,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟交易历史
|
||||||
|
let mockOrders = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
order_no: 'ORD20240101001',
|
||||||
|
stock_code: '600036',
|
||||||
|
stock_name: '招商银行',
|
||||||
|
order_type: 'BUY',
|
||||||
|
price_type: 'MARKET',
|
||||||
|
order_price: 42.50,
|
||||||
|
order_qty: 1000,
|
||||||
|
filled_qty: 1000,
|
||||||
|
filled_price: 42.50,
|
||||||
|
filled_amount: 42500,
|
||||||
|
commission: 12.75,
|
||||||
|
stamp_tax: 0,
|
||||||
|
transfer_fee: 0.42,
|
||||||
|
total_fee: 13.17,
|
||||||
|
status: 'FILLED',
|
||||||
|
reject_reason: null,
|
||||||
|
order_time: '2024-01-15T09:30:00Z',
|
||||||
|
filled_time: '2024-01-15T09:30:05Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
order_no: 'ORD20240102001',
|
||||||
|
stock_code: '000001',
|
||||||
|
stock_name: '平安银行',
|
||||||
|
order_type: 'BUY',
|
||||||
|
price_type: 'LIMIT',
|
||||||
|
order_price: 12.30,
|
||||||
|
order_qty: 2000,
|
||||||
|
filled_qty: 2000,
|
||||||
|
filled_price: 12.30,
|
||||||
|
filled_amount: 24600,
|
||||||
|
commission: 7.38,
|
||||||
|
stamp_tax: 0,
|
||||||
|
transfer_fee: 0.25,
|
||||||
|
total_fee: 7.63,
|
||||||
|
status: 'FILLED',
|
||||||
|
reject_reason: null,
|
||||||
|
order_time: '2024-01-16T10:15:00Z',
|
||||||
|
filled_time: '2024-01-16T10:15:10Z'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const simulationHandlers = [
|
||||||
|
// ==================== 获取模拟账户信息 ====================
|
||||||
|
http.get('/api/simulation/account', async () => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
// 未登录时返回401
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Mock] 获取模拟账户信息:', currentUser);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: mockTradingAccount
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 获取持仓列表 ====================
|
||||||
|
http.get('/api/simulation/positions', async () => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Mock] 获取持仓列表');
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: mockPositions
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 获取交易订单历史 ====================
|
||||||
|
http.get('/api/simulation/orders', async ({ request }) => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const limit = parseInt(url.searchParams.get('limit') || '100');
|
||||||
|
|
||||||
|
console.log('[Mock] 获取交易订单历史, limit:', limit);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: mockOrders.slice(0, limit)
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 下单(买入/卖出)====================
|
||||||
|
http.post('/api/simulation/place-order', async ({ request }) => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await request.json();
|
||||||
|
console.log('[Mock] 下单请求:', body);
|
||||||
|
|
||||||
|
const { stock_code, order_type, order_qty, price_type } = body;
|
||||||
|
|
||||||
|
// 生成订单号
|
||||||
|
const orderNo = 'ORD' + Date.now();
|
||||||
|
|
||||||
|
// 创建新订单
|
||||||
|
const newOrder = {
|
||||||
|
id: mockOrders.length + 1,
|
||||||
|
order_no: orderNo,
|
||||||
|
stock_code: stock_code,
|
||||||
|
stock_name: '模拟股票', // 实际应该查询股票名称
|
||||||
|
order_type: order_type,
|
||||||
|
price_type: price_type,
|
||||||
|
order_price: 0,
|
||||||
|
order_qty: order_qty,
|
||||||
|
filled_qty: order_qty,
|
||||||
|
filled_price: 0,
|
||||||
|
filled_amount: 0,
|
||||||
|
commission: 0,
|
||||||
|
stamp_tax: 0,
|
||||||
|
transfer_fee: 0,
|
||||||
|
total_fee: 0,
|
||||||
|
status: 'FILLED',
|
||||||
|
reject_reason: null,
|
||||||
|
order_time: new Date().toISOString(),
|
||||||
|
filled_time: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加到订单列表
|
||||||
|
mockOrders.unshift(newOrder);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '下单成功',
|
||||||
|
data: {
|
||||||
|
order_no: orderNo,
|
||||||
|
order_id: newOrder.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 撤销订单 ====================
|
||||||
|
http.post('/api/simulation/cancel-order/:orderId', async ({ params }) => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orderId } = params;
|
||||||
|
console.log('[Mock] 撤销订单:', orderId);
|
||||||
|
|
||||||
|
// 查找并更新订单状态
|
||||||
|
const order = mockOrders.find(o => o.id.toString() === orderId || o.order_no === orderId);
|
||||||
|
if (order) {
|
||||||
|
order.status = 'CANCELLED';
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: '撤单成功'
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 获取资产统计数据 ====================
|
||||||
|
http.get('/api/simulation/statistics', async ({ request }) => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const days = parseInt(url.searchParams.get('days') || '30');
|
||||||
|
|
||||||
|
console.log('[Mock] 获取资产统计, days:', days);
|
||||||
|
|
||||||
|
// 生成模拟的资产历史数据
|
||||||
|
const dailyReturns = [];
|
||||||
|
const baseAssets = 1000000;
|
||||||
|
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - (days - 1 - i));
|
||||||
|
|
||||||
|
// 生成随机波动
|
||||||
|
const randomChange = (Math.random() - 0.5) * 0.02; // ±1%
|
||||||
|
const assets = baseAssets * (1 + randomChange * i / days);
|
||||||
|
|
||||||
|
dailyReturns.push({
|
||||||
|
date: date.toISOString().split('T')[0],
|
||||||
|
closing_assets: assets,
|
||||||
|
total_assets: assets,
|
||||||
|
daily_profit: assets - baseAssets,
|
||||||
|
daily_profit_rate: ((assets - baseAssets) / baseAssets * 100).toFixed(2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
daily_returns: dailyReturns,
|
||||||
|
summary: {
|
||||||
|
total_profit: 0,
|
||||||
|
total_profit_rate: 0,
|
||||||
|
win_rate: 50,
|
||||||
|
max_drawdown: -5.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 获取交易记录 ====================
|
||||||
|
http.get('/api/simulation/transactions', async ({ request }) => {
|
||||||
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
|
const currentUser = getCurrentUser();
|
||||||
|
|
||||||
|
if (!currentUser) {
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: '未登录'
|
||||||
|
}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const limit = parseInt(url.searchParams.get('limit') || '50');
|
||||||
|
|
||||||
|
console.log('[Mock] 获取交易记录, limit:', limit);
|
||||||
|
|
||||||
|
// 返回已成交的订单作为交易记录
|
||||||
|
const transactions = mockOrders
|
||||||
|
.filter(order => order.status === 'FILLED')
|
||||||
|
.slice(0, limit);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: transactions
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// ==================== 搜索股票 ====================
|
||||||
|
http.get('/api/stocks/search', async ({ request }) => {
|
||||||
|
await delay(200);
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const keyword = url.searchParams.get('q') || '';
|
||||||
|
const limit = parseInt(url.searchParams.get('limit') || '10');
|
||||||
|
|
||||||
|
console.log('[Mock] 搜索股票:', keyword);
|
||||||
|
|
||||||
|
// 模拟股票数据
|
||||||
|
const allStocks = [
|
||||||
|
{ stock_code: '000001', stock_name: '平安银行', current_price: 12.50, pinyin_abbr: 'payh', security_type: 'A股', exchange: '深交所' },
|
||||||
|
{ stock_code: '000002', stock_name: '万科A', current_price: 8.32, pinyin_abbr: 'wka', security_type: 'A股', exchange: '深交所' },
|
||||||
|
{ stock_code: '600036', stock_name: '招商银行', current_price: 42.80, pinyin_abbr: 'zsyh', security_type: 'A股', exchange: '上交所' },
|
||||||
|
{ stock_code: '600519', stock_name: '贵州茅台', current_price: 1680.50, pinyin_abbr: 'gzmt', security_type: 'A股', exchange: '上交所' },
|
||||||
|
{ stock_code: '601318', stock_name: '中国平安', current_price: 45.20, pinyin_abbr: 'zgpa', security_type: 'A股', exchange: '上交所' },
|
||||||
|
{ stock_code: '688256', stock_name: '寒武纪', current_price: 1394.94, pinyin_abbr: 'hwj', security_type: 'A股', exchange: '上交所科创板' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 过滤股票
|
||||||
|
const results = allStocks.filter(stock =>
|
||||||
|
stock.stock_code.includes(keyword) ||
|
||||||
|
stock.stock_name.includes(keyword) ||
|
||||||
|
stock.pinyin_abbr.includes(keyword.toLowerCase())
|
||||||
|
).slice(0, limit);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: results
|
||||||
|
});
|
||||||
|
})
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user