|
|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs, Table, Modal } from 'antd';
|
|
|
|
|
import { message } from 'antd';
|
|
|
|
|
import { DatabaseOutlined, KeyOutlined, SettingOutlined, SaveOutlined, ToolOutlined, RobotOutlined, EditOutlined } from '@ant-design/icons';
|
|
|
|
|
import { DatabaseOutlined, KeyOutlined, SettingOutlined, SaveOutlined, ToolOutlined, RobotOutlined, EditOutlined, CloudDownloadOutlined, ClearOutlined, ThunderboltOutlined, FileTextOutlined, CodeOutlined } from '@ant-design/icons';
|
|
|
|
|
import './AdminConfig.css';
|
|
|
|
|
|
|
|
|
|
const { Option } = Select;
|
|
|
|
|
@ -14,12 +14,269 @@ const AdminConfig = () => {
|
|
|
|
|
const [aiModelModalVisible, setAiModelModalVisible] = useState(false);
|
|
|
|
|
const [currentAiModel, setCurrentAiModel] = useState(null);
|
|
|
|
|
const [aiModelForm] = Form.useForm();
|
|
|
|
|
const [cacheLoading, setCacheLoading] = useState(false);
|
|
|
|
|
const [cacheStats, setCacheStats] = useState({ total: 0, valid: 0, expired: 0 });
|
|
|
|
|
const [cacheProgress, setCacheProgress] = useState({ current: 0, total: 0, name: '' });
|
|
|
|
|
const [dbCacheLoading, setDbCacheLoading] = useState(false);
|
|
|
|
|
const [dbCacheProgress, setDbCacheProgress] = useState({ current: 0, total: 0, name: '' });
|
|
|
|
|
const [symbolInput, setSymbolInput] = useState('');
|
|
|
|
|
const [singleSymbolLoading, setSingleSymbolLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
// 组件挂载时获取配置
|
|
|
|
|
// 组件挂载时获取配置和缓存统计
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchConfig();
|
|
|
|
|
updateCacheStats();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 更新缓存统计
|
|
|
|
|
const updateCacheStats = () => {
|
|
|
|
|
const stats = JSON.parse(localStorage.getItem('dataCacheStats') || '{"total":0,"valid":0,"expired":0}');
|
|
|
|
|
setCacheStats(stats);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 一键获取数据并缓存
|
|
|
|
|
const fetchAllDataForCache = async () => {
|
|
|
|
|
setCacheLoading(true);
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
const cacheResults = [];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
messageApi.info('开始获取数据并缓存...');
|
|
|
|
|
|
|
|
|
|
// 1. 获取市场概览数据
|
|
|
|
|
setCacheProgress({ current: 1, total: 4, name: '市场概览数据' });
|
|
|
|
|
try {
|
|
|
|
|
const overviewResponse = await fetch(`${API_BASE_URL}/market/overview`);
|
|
|
|
|
if (overviewResponse.ok) {
|
|
|
|
|
const overviewData = await overviewResponse.json();
|
|
|
|
|
localStorage.setItem('cached_overview', JSON.stringify({
|
|
|
|
|
data: overviewData.data,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
expiresAt: Date.now() + 5 * 60 * 1000 // 5分钟过期
|
|
|
|
|
}));
|
|
|
|
|
cacheResults.push({ name: '市场概览', status: 'success' });
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存市场概览失败:', error);
|
|
|
|
|
cacheResults.push({ name: '市场概览', status: 'error', error: error.message });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 获取风险预警数据
|
|
|
|
|
setCacheProgress({ current: 2, total: 4, name: '风险预警数据' });
|
|
|
|
|
try {
|
|
|
|
|
const alertsResponse = await fetch(`${API_BASE_URL}/market/alerts`);
|
|
|
|
|
if (alertsResponse.ok) {
|
|
|
|
|
const alertsData = await alertsResponse.json();
|
|
|
|
|
localStorage.setItem('cached_alerts', JSON.stringify({
|
|
|
|
|
data: alertsData.data,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
expiresAt: Date.now() + 3 * 60 * 1000 // 3分钟过期
|
|
|
|
|
}));
|
|
|
|
|
cacheResults.push({ name: '风险预警', status: 'success' });
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存风险预警失败:', error);
|
|
|
|
|
cacheResults.push({ name: '风险预警', status: 'error', error: error.message });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 获取热门品种详情(取前5个)
|
|
|
|
|
setCacheProgress({ current: 3, total: 4, name: '热门品种详情' });
|
|
|
|
|
try {
|
|
|
|
|
const overviewItem = localStorage.getItem('cached_overview');
|
|
|
|
|
if (overviewItem) {
|
|
|
|
|
const overview = JSON.parse(overviewItem);
|
|
|
|
|
const hotFutures = overview.data?.slice(0, 5) || [];
|
|
|
|
|
const detailPromises = hotFutures.map(async (item) => {
|
|
|
|
|
try {
|
|
|
|
|
const detailResponse = await fetch(`${API_BASE_URL}/market/detail/${item.code}`);
|
|
|
|
|
if (detailResponse.ok) {
|
|
|
|
|
const detailData = await detailResponse.json();
|
|
|
|
|
localStorage.setItem(`cached_detail_${item.code}`, JSON.stringify({
|
|
|
|
|
data: detailData.data,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
expiresAt: Date.now() + 10 * 60 * 1000 // 10分钟过期
|
|
|
|
|
}));
|
|
|
|
|
return { code: item.code, status: 'success' };
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return { code: item.code, status: 'error' };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
await Promise.all(detailPromises);
|
|
|
|
|
cacheResults.push({ name: '热门品种详情', status: 'success', count: hotFutures.length });
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存品种详情失败:', error);
|
|
|
|
|
cacheResults.push({ name: '热门品种详情', status: 'error', error: error.message });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 获取K线数据(主要周期)
|
|
|
|
|
setCacheProgress({ current: 4, total: 4, name: 'K线数据' });
|
|
|
|
|
try {
|
|
|
|
|
const overviewItem = localStorage.getItem('cached_overview');
|
|
|
|
|
if (overviewItem) {
|
|
|
|
|
const overview = JSON.parse(overviewItem);
|
|
|
|
|
const topFutures = overview.data?.slice(0, 3) || [];
|
|
|
|
|
const periods = ['1D', '1H'];
|
|
|
|
|
const klinePromises = [];
|
|
|
|
|
|
|
|
|
|
topFutures.forEach(item => {
|
|
|
|
|
periods.forEach(period => {
|
|
|
|
|
klinePromises.push(
|
|
|
|
|
fetch(`${API_BASE_URL}/market/klines/${item.code}?period=${period}`)
|
|
|
|
|
.then(res => res.ok ? res.json() : null)
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data) {
|
|
|
|
|
localStorage.setItem(`cached_kline_${item.code}_${period}`, JSON.stringify({
|
|
|
|
|
data: data.data,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
expiresAt: Date.now() + 30 * 60 * 1000 // 30分钟过期
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Promise.all(klinePromises);
|
|
|
|
|
cacheResults.push({ name: 'K线数据', status: 'success', count: topFutures.length * periods.length });
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存K线数据失败:', error);
|
|
|
|
|
cacheResults.push({ name: 'K线数据', status: 'error', error: error.message });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新缓存统计
|
|
|
|
|
const successCount = cacheResults.filter(r => r.status === 'success').length;
|
|
|
|
|
const newStats = {
|
|
|
|
|
total: cacheResults.length,
|
|
|
|
|
valid: successCount,
|
|
|
|
|
expired: cacheResults.length - successCount,
|
|
|
|
|
lastUpdate: Date.now()
|
|
|
|
|
};
|
|
|
|
|
localStorage.setItem('dataCacheStats', JSON.stringify(newStats));
|
|
|
|
|
setCacheStats(newStats);
|
|
|
|
|
|
|
|
|
|
messageApi.success(`数据缓存完成!成功: ${successCount}/${cacheResults.length}`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存数据失败:', error);
|
|
|
|
|
messageApi.error('缓存数据失败: ' + error.message);
|
|
|
|
|
} finally {
|
|
|
|
|
setCacheLoading(false);
|
|
|
|
|
setCacheProgress({ current: 0, total: 0, name: '' });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 清除所有缓存
|
|
|
|
|
const clearAllCache = () => {
|
|
|
|
|
try {
|
|
|
|
|
// 清除所有缓存相关的localStorage项
|
|
|
|
|
const keysToRemove = [];
|
|
|
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
|
|
|
const key = localStorage.key(i);
|
|
|
|
|
if (key && (key.startsWith('cached_') || key === 'dataCacheStats')) {
|
|
|
|
|
keysToRemove.push(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
|
|
|
|
|
|
|
|
// 更新统计
|
|
|
|
|
const newStats = { total: 0, valid: 0, expired: 0, lastUpdate: null };
|
|
|
|
|
localStorage.setItem('dataCacheStats', JSON.stringify(newStats));
|
|
|
|
|
setCacheStats(newStats);
|
|
|
|
|
|
|
|
|
|
messageApi.success('所有缓存已清除');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
messageApi.error('清除缓存失败: ' + error.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 一键缓存所有合约数据到数据库
|
|
|
|
|
const cacheAllContractsToDB = async () => {
|
|
|
|
|
setDbCacheLoading(true);
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
messageApi.info('开始批量缓存所有合约到数据库...');
|
|
|
|
|
setDbCacheProgress({ current: 1, total: 3, name: '获取合约列表' });
|
|
|
|
|
|
|
|
|
|
// 调用后端批量缓存API
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/cache-all`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error('批量缓存请求失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
const { success, total, klineCached } = result.data;
|
|
|
|
|
messageApi.success(`数据库缓存完成!成功: ${success}/${total},K线: ${klineCached}条`);
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(result.message || '批量缓存失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存到数据库失败:', error);
|
|
|
|
|
messageApi.error('缓存到数据库失败: ' + error.message);
|
|
|
|
|
} finally {
|
|
|
|
|
setDbCacheLoading(false);
|
|
|
|
|
setDbCacheProgress({ current: 0, total: 0, name: '' });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 缓存指定合约数据到数据库
|
|
|
|
|
const cacheSymbolToDB = async () => {
|
|
|
|
|
if (!symbolInput.trim()) {
|
|
|
|
|
messageApi.warning('请输入合约代码');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setSingleSymbolLoading(true);
|
|
|
|
|
const API_BASE_URL = 'http://localhost:3007/api';
|
|
|
|
|
const symbol = symbolInput.trim().toUpperCase();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
messageApi.info(`开始缓存合约 ${symbol} 数据到数据库...`);
|
|
|
|
|
|
|
|
|
|
// 调用后端单合约缓存API(强制刷新)
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/market/cache/${symbol}`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
periods: ['1H', '4H', '1D']
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const error = await response.json();
|
|
|
|
|
throw new Error(error.message || '缓存失败');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
const klineSuccess = result.data.klines.filter((k) => k.success).length;
|
|
|
|
|
messageApi.success(`合约 ${symbol} 数据缓存完成!详情: ✓,K线: ${klineSuccess}/${result.data.klines.length}`);
|
|
|
|
|
setSymbolInput(''); // 清空输入
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error(result.message || '缓存失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('缓存合约到数据库失败:', error);
|
|
|
|
|
messageApi.error('缓存合约到数据库失败: ' + error.message);
|
|
|
|
|
} finally {
|
|
|
|
|
setSingleSymbolLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 数据库配置
|
|
|
|
|
const databaseConfig = {
|
|
|
|
|
// MySQL配置
|
|
|
|
|
@ -1600,6 +1857,251 @@ const AdminConfig = () => {
|
|
|
|
|
</Card>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: <span><CloudDownloadOutlined /> 数据缓存</span>,
|
|
|
|
|
key: 'cache',
|
|
|
|
|
children: (
|
|
|
|
|
<>
|
|
|
|
|
{/* 缓存统计 */}
|
|
|
|
|
<Card title="缓存统计" className="admin-config-card" style={{ marginBottom: 24 }}>
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '20px', background: '#f6ffed', borderRadius: '8px' }}>
|
|
|
|
|
<div style={{ fontSize: '32px', fontWeight: 'bold', color: '#52c41a' }}>{cacheStats.valid}</div>
|
|
|
|
|
<div style={{ color: '#666', marginTop: '8px' }}>有效缓存</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '20px', background: '#fff7e6', borderRadius: '8px' }}>
|
|
|
|
|
<div style={{ fontSize: '32px', fontWeight: 'bold', color: '#fa8c16' }}>{cacheStats.expired}</div>
|
|
|
|
|
<div style={{ color: '#666', marginTop: '8px' }}>过期缓存</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '20px', background: '#e6f7ff', borderRadius: '8px' }}>
|
|
|
|
|
<div style={{ fontSize: '32px', fontWeight: 'bold', color: '#1890ff' }}>{cacheStats.total}</div>
|
|
|
|
|
<div style={{ color: '#666', marginTop: '8px' }}>缓存总数</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '20px', background: '#f9f0ff', borderRadius: '8px' }}>
|
|
|
|
|
<div style={{ fontSize: '14px', fontWeight: 'bold', color: '#722ed1' }}>
|
|
|
|
|
{cacheStats.lastUpdate ? new Date(cacheStats.lastUpdate).toLocaleString() : '从未'}
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ color: '#666', marginTop: '8px' }}>上次更新</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* 一键缓存 */}
|
|
|
|
|
<Card title="一键获取数据并缓存" className="admin-config-card" style={{ marginBottom: 24 }}>
|
|
|
|
|
<Alert
|
|
|
|
|
message="数据缓存说明"
|
|
|
|
|
description="点击'一键获取数据'按钮,系统将自动获取市场概览、风险预警、热门品种详情和K线数据并缓存到本地,以提升后续访问速度。"
|
|
|
|
|
type="info"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{cacheProgress.total > 0 && (
|
|
|
|
|
<div style={{ marginBottom: 16, padding: '12px', background: '#f0f0f0', borderRadius: '6px' }}>
|
|
|
|
|
<div style={{ marginBottom: 8 }}>
|
|
|
|
|
<ThunderboltOutlined style={{ color: '#1890ff', marginRight: 8 }} />
|
|
|
|
|
正在缓存: {cacheProgress.name} ({cacheProgress.current}/{cacheProgress.total})
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ width: '100%', height: '8px', background: '#d9d9d9', borderRadius: '4px', overflow: 'hidden' }}>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
width: `${(cacheProgress.current / cacheProgress.total) * 100}%`,
|
|
|
|
|
height: '100%',
|
|
|
|
|
background: '#1890ff',
|
|
|
|
|
transition: 'width 0.3s ease'
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<CloudDownloadOutlined />}
|
|
|
|
|
loading={cacheLoading}
|
|
|
|
|
onClick={fetchAllDataForCache}
|
|
|
|
|
size="large"
|
|
|
|
|
style={{ marginRight: 16 }}
|
|
|
|
|
>
|
|
|
|
|
一键获取数据
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="default"
|
|
|
|
|
icon={<ClearOutlined />}
|
|
|
|
|
onClick={clearAllCache}
|
|
|
|
|
size="large"
|
|
|
|
|
danger
|
|
|
|
|
>
|
|
|
|
|
清除所有缓存
|
|
|
|
|
</Button>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
<div style={{ color: '#666' }}>
|
|
|
|
|
<h4>缓存内容包括:</h4>
|
|
|
|
|
<ul style={{ lineHeight: '2' }}>
|
|
|
|
|
<li>📊 市场概览数据(有效期:5分钟)</li>
|
|
|
|
|
<li>⚠️ 风险预警数据(有效期:3分钟)</li>
|
|
|
|
|
<li>📈 热门品种详情(前5个品种,有效期:10分钟)</li>
|
|
|
|
|
<li>📉 K线数据(主要品种,有效期:30分钟)</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* 一键缓存所有合约到数据库 */}
|
|
|
|
|
<Card title="一键缓存所有合约到数据库" className="admin-config-card" style={{ marginBottom: 24 }}>
|
|
|
|
|
<Alert
|
|
|
|
|
message="数据库缓存说明"
|
|
|
|
|
description="点击'缓存所有合约到数据库'按钮,系统将自动获取所有合约的详情和K线数据,并持久化存储到MySQL数据库和Redis缓存中。此操作会触发后端API自动存储数据。"
|
|
|
|
|
type="warning"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{dbCacheProgress.total > 0 && (
|
|
|
|
|
<div style={{ marginBottom: 16, padding: '12px', background: '#f0f0f0', borderRadius: '6px' }}>
|
|
|
|
|
<div style={{ marginBottom: 8 }}>
|
|
|
|
|
<ThunderboltOutlined style={{ color: '#fa8c16', marginRight: 8 }} />
|
|
|
|
|
正在处理: {dbCacheProgress.name} ({dbCacheProgress.current}/{dbCacheProgress.total})
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ width: '100%', height: '8px', background: '#d9d9d9', borderRadius: '4px', overflow: 'hidden' }}>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
width: `${(dbCacheProgress.current / dbCacheProgress.total) * 100}%`,
|
|
|
|
|
height: '100%',
|
|
|
|
|
background: '#fa8c16',
|
|
|
|
|
transition: 'width 0.3s ease'
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<DatabaseOutlined />}
|
|
|
|
|
loading={dbCacheLoading}
|
|
|
|
|
onClick={cacheAllContractsToDB}
|
|
|
|
|
size="large"
|
|
|
|
|
danger
|
|
|
|
|
style={{ marginRight: 16 }}
|
|
|
|
|
>
|
|
|
|
|
缓存所有合约到数据库
|
|
|
|
|
</Button>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
<div style={{ color: '#666' }}>
|
|
|
|
|
<h4>数据存储位置:</h4>
|
|
|
|
|
<ul style={{ lineHeight: '2' }}>
|
|
|
|
|
<li>🗄️ MySQL数据库 - 持久化存储所有合约数据</li>
|
|
|
|
|
<li>⚡ Redis缓存 - 高速缓存热点数据</li>
|
|
|
|
|
<li>📊 合约详情数据(market_data表)</li>
|
|
|
|
|
<li>📈 K线数据(多个周期)</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p style={{ marginTop: 12, color: '#999' }}>
|
|
|
|
|
<FileTextOutlined style={{ marginRight: 8 }} />
|
|
|
|
|
注意:此操作可能需要较长时间,取决于合约数量和网络状况。
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* 缓存指定合约到数据库 */}
|
|
|
|
|
<Card title="缓存指定合约到数据库" className="admin-config-card" style={{ marginBottom: 24 }}>
|
|
|
|
|
<Alert
|
|
|
|
|
message="单合约缓存"
|
|
|
|
|
description="输入合约代码(如 AU、CU、RB 等),系统将获取该合约的详情和K线数据并缓存到数据库。"
|
|
|
|
|
type="info"
|
|
|
|
|
showIcon
|
|
|
|
|
style={{ marginBottom: 16 }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]} align="middle">
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="请输入合约代码,如:AU"
|
|
|
|
|
value={symbolInput}
|
|
|
|
|
onChange={(e) => setSymbolInput(e.target.value)}
|
|
|
|
|
onPressEnter={cacheSymbolToDB}
|
|
|
|
|
size="large"
|
|
|
|
|
prefix={<CodeOutlined />}
|
|
|
|
|
style={{ textTransform: 'uppercase' }}
|
|
|
|
|
/>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<CloudDownloadOutlined />}
|
|
|
|
|
loading={singleSymbolLoading}
|
|
|
|
|
onClick={cacheSymbolToDB}
|
|
|
|
|
size="large"
|
|
|
|
|
>
|
|
|
|
|
缓存指定合约
|
|
|
|
|
</Button>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
|
|
|
|
<div style={{ color: '#666' }}>
|
|
|
|
|
<h4>常用合约代码示例:</h4>
|
|
|
|
|
<Row gutter={[8, 8]}>
|
|
|
|
|
{['AU', 'CU', 'RB', 'AG', 'ZN', 'NI', 'AL', 'PB', 'SN', 'HC'].map(code => (
|
|
|
|
|
<Col key={code}>
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => setSymbolInput(code)}
|
|
|
|
|
style={{ marginBottom: 8 }}
|
|
|
|
|
>
|
|
|
|
|
{code}
|
|
|
|
|
</Button>
|
|
|
|
|
</Col>
|
|
|
|
|
))}
|
|
|
|
|
</Row>
|
|
|
|
|
<p style={{ marginTop: 12 }}>
|
|
|
|
|
<FileTextOutlined style={{ marginRight: 8 }} />
|
|
|
|
|
缓存内容包括:合约详情、1小时K线、4小时K线、日线数据
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* 缓存管理 */}
|
|
|
|
|
<Card title="缓存管理" className="admin-config-card">
|
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Card type="inner" title="缓存存储位置" size="small">
|
|
|
|
|
<p>数据缓存存储在浏览器的 <code>localStorage</code> 中</p>
|
|
|
|
|
<p>缓存键前缀: <code>cached_</code></p>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
<Card type="inner" title="缓存策略" size="small">
|
|
|
|
|
<p>• 数据在有效期内优先使用缓存</p>
|
|
|
|
|
<p>• 过期数据会自动重新获取</p>
|
|
|
|
|
<p>• 支持强制刷新获取最新数据</p>
|
|
|
|
|
</Card>
|
|
|
|
|
</Col>
|
|
|
|
|
</Row>
|
|
|
|
|
</Card>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
|