fix: 增加启动文档,尝试解决配置问题bug

master
Lxy 3 months ago
parent 8bcd039e9d
commit fc402d80d9

@ -14,188 +14,286 @@ export const fetchMarketOverview = async () => {
try {
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const overview = await dataSource.getMarketOverview();
// 转换为前端需要的格式
return overview.map(item => ({
code: item.symbol,
name: item.name,
currentPrice: item.price,
changePercent: item.change_percent,
winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率
atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR
adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
// 检查是否有可用的数据源
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
if (!hasAvailableDataSource) {
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
// 尝试使用TQSDK数据源
if (dataSourceConfig.tqsdk?.enabled) {
try {
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
// 使用用户指定的合约列表
const overview = [];
for (const future of futuresList) {
try {
// 构建合约符号
const symbol = `${future.exchange}.${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
// 获取合约详情和实时行情
const tick = await dataSource.getTickData(symbol);
overview.push({
code: future.code,
name: future.name,
currentPrice: tick.last_price,
changePercent: tick.price_change / tick.pre_close * 100,
winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率
atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR
adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
}
},
tradingAdvice: {
entry: tick.last_price,
stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
},
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
});
} catch (error) {
console.error(`获取合约${future.code}行情失败:`, error);
// 跳过获取失败的合约
continue;
}
}
},
tradingAdvice: {
entry: item.price,
stopLoss: item.price * (1 - 0.02 * (Math.random() + 0.5)),
target: item.price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: item.price * (1 + 0.05 * (Math.random() + 0.5)),
support: item.price * (1 - 0.05 * (Math.random() + 0.5))
},
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
}));
} catch (error) {
console.error('获取市场概览失败:', error);
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 检查是否启用了测试数据源
if (dataSourceConfig.test && dataSourceConfig.test.enabled) {
// 启用了测试数据源,使用测试数据
return overview;
} catch (error) {
console.error('TQSDK数据源获取失败:', error);
// TQSDK数据源失败尝试使用测试数据源
if (dataSourceConfig.test?.enabled) {
console.log('切换到测试数据源');
// 启用了测试数据源,使用测试数据
await new Promise(resolve => setTimeout(resolve, 300));
return generateFuturesOverview();
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取市场概览失败,所有数据源均不可用');
}
}
} else if (dataSourceConfig.test?.enabled) {
// 直接使用测试数据源
console.log('使用测试数据源');
await new Promise(resolve => setTimeout(resolve, 300));
return generateFuturesOverview();
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取市场概览失败,请检查数据源配置');
// 无可用数据源
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
} catch (error) {
console.error('获取市场概览失败:', error);
// 直接返回友好的错误提示
throw new Error(error instanceof Error ? error.message : '获取市场概览失败,请检查数据源配置');
}
};
// 获取品种详情
export const fetchMarketDetail = async (symbol: string) => {
try {
// 查找合约信息
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error('品种不存在');
}
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const contract = await dataSource.getContractDetail(symbol);
const tick = await dataSource.getTickData(symbol);
// 转换为前端需要的格式
return {
code: contract.symbol,
name: contract.name,
fullName: `${contract.name}-${contract.symbol}605`,
currentPrice: tick.last_price,
changePercent: tick.price_change / tick.pre_close * 100,
winRate: Math.floor(Math.random() * 50) + 30,
atr: +(Math.random() * 5 + 0.5).toFixed(2),
adx: Math.floor(Math.random() * 60) + 10,
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
// 检查是否有可用的数据源
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
if (!hasAvailableDataSource) {
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
// 尝试使用TQSDK数据源
if (dataSourceConfig.tqsdk?.enabled) {
try {
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
// 构建合约符号
const contractSymbol = `${future.exchange}.${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
// 获取合约详情和实时行情
const tick = await dataSource.getTickData(contractSymbol);
// 转换为前端需要的格式
return {
code: future.code,
name: future.name,
fullName: `${future.name}-${future.code}605`,
currentPrice: tick.last_price,
changePercent: tick.price_change / tick.pre_close * 100,
winRate: Math.floor(Math.random() * 50) + 30,
atr: +(Math.random() * 5 + 0.5).toFixed(2),
adx: Math.floor(Math.random() * 60) + 10,
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
}
},
indicators: {
macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)],
rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`,
bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)],
kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)]
},
tradingAdvice: {
entry: tick.last_price,
stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
},
riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
};
} catch (error) {
console.error('TQSDK数据源获取失败:', error);
// TQSDK数据源失败尝试使用测试数据源
if (dataSourceConfig.test?.enabled) {
console.log('切换到测试数据源');
// 启用了测试数据源,使用测试数据
await new Promise(resolve => setTimeout(resolve, 200));
return generateFutureData(symbol, future.name);
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取品种详情失败,所有数据源均不可用');
}
},
indicators: {
macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)],
rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`,
bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)],
kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)]
},
tradingAdvice: {
entry: tick.last_price,
stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
},
riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
};
} catch (error) {
console.error(`获取品种${symbol}详情失败:`, error);
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 检查是否启用了测试数据源
if (dataSourceConfig.test && dataSourceConfig.test.enabled) {
// 启用了测试数据源,使用测试数据
await new Promise(resolve => setTimeout(resolve, 200));
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error('品种不存在');
}
} else if (dataSourceConfig.test?.enabled) {
// 直接使用测试数据源
console.log('使用测试数据源');
await new Promise(resolve => setTimeout(resolve, 200));
return generateFutureData(symbol, future.name);
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取品种详情失败,请检查数据源配置');
// 无可用数据源
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
} catch (error) {
console.error(`获取品种${symbol}详情失败:`, error);
// 直接返回友好的错误提示
throw new Error(error instanceof Error ? error.message : '获取品种详情失败,请检查数据源配置');
}
};
// 获取K线数据
export const fetchKlineData = async (symbol: string, period: string) => {
try {
// 查找合约信息
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error('品种不存在');
}
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const klineData = await dataSource.getKlineData(symbol, period, 30);
// 转换为前端需要的格式
return klineData.map(item => ({
timestamp: item.datetime / 1000000000, // 转换为秒
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume
}));
} catch (error) {
console.error(`获取合约${symbol}K线数据失败:`, error);
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 检查是否启用了测试数据源
if (dataSourceConfig.test && dataSourceConfig.test.enabled) {
// 启用了测试数据源,使用测试数据
// 检查是否有可用的数据源
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
if (!hasAvailableDataSource) {
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
// 尝试使用TQSDK数据源
if (dataSourceConfig.tqsdk?.enabled) {
try {
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
// 构建合约符号
const contractSymbol = `${future.exchange}.${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
// 获取K线数据
const klineData = await dataSource.getKlineData(contractSymbol, period, 30);
// 转换为前端需要的格式
return klineData.map(item => ({
timestamp: item.datetime / 1000000000, // 转换为秒
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume
}));
} catch (error) {
console.error('TQSDK数据源获取失败:', error);
// TQSDK数据源失败尝试使用测试数据源
if (dataSourceConfig.test?.enabled) {
console.log('切换到测试数据源');
// 启用了测试数据源,使用测试数据
await new Promise(resolve => setTimeout(resolve, 200));
return generateKlineData(30);
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取K线数据失败所有数据源均不可用');
}
}
} else if (dataSourceConfig.test?.enabled) {
// 直接使用测试数据源
console.log('使用测试数据源');
await new Promise(resolve => setTimeout(resolve, 200));
return generateKlineData(30);
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取K线数据失败请检查数据源配置');
// 无可用数据源
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
} catch (error) {
console.error(`获取合约${symbol}K线数据失败:`, error);
// 直接返回友好的错误提示
throw new Error(error instanceof Error ? error.message : '获取K线数据失败请检查数据源配置');
}
};
@ -204,27 +302,70 @@ export const fetchMarketHotspots = async () => {
try {
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const overview = await dataSource.getMarketOverview();
// 按涨跌幅排序返回前10个
return overview
.sort((a, b) => Math.abs(b.change_percent) - Math.abs(a.change_percent))
.slice(0, 10)
.map(item => ({
symbol: item.symbol,
name: item.name,
change: item.change_percent,
volume: item.volume
}));
} catch (error) {
console.error('获取市场热点失败:', error);
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 检查是否启用了测试数据源
if (dataSourceConfig.test && dataSourceConfig.test.enabled) {
// 启用了测试数据源,使用测试数据
// 检查是否有可用的数据源
const hasAvailableDataSource = dataSourceConfig.tqsdk?.enabled || dataSourceConfig.test?.enabled;
if (!hasAvailableDataSource) {
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
// 尝试使用TQSDK数据源
if (dataSourceConfig.tqsdk?.enabled) {
try {
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
// 使用用户指定的合约列表
const hotspots = [];
for (const future of futuresList) {
try {
// 构建合约符号
const symbol = `${future.exchange}.${future.code}${new Date().getFullYear().toString().slice(-2)}05`;
// 获取合约详情和实时行情
const tick = await dataSource.getTickData(symbol);
hotspots.push({
symbol: future.code,
name: future.name,
change: tick.price_change / tick.pre_close * 100,
volume: tick.volume
});
} catch (error) {
console.error(`获取合约${future.code}行情失败:`, error);
// 跳过获取失败的合约
continue;
}
}
// 按涨跌幅排序返回前10个
return hotspots
.sort((a, b) => Math.abs(b.change) - Math.abs(a.change))
.slice(0, 10);
} catch (error) {
console.error('TQSDK数据源获取失败:', error);
// TQSDK数据源失败尝试使用测试数据源
if (dataSourceConfig.test?.enabled) {
console.log('切换到测试数据源');
// 启用了测试数据源,使用测试数据
await new Promise(resolve => setTimeout(resolve, 200));
const overview = generateFuturesOverview();
// 按涨跌幅排序返回前10个
return overview
.sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent))
.slice(0, 10)
.map(item => ({
symbol: item.code,
name: item.name,
change: item.changePercent,
volume: Math.floor(Math.random() * 1000000) + 100000
}));
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取市场热点失败,所有数据源均不可用');
}
}
} else if (dataSourceConfig.test?.enabled) {
// 直接使用测试数据源
console.log('使用测试数据源');
await new Promise(resolve => setTimeout(resolve, 200));
const overview = generateFuturesOverview();
// 按涨跌幅排序返回前10个
@ -238,9 +379,13 @@ export const fetchMarketHotspots = async () => {
volume: Math.floor(Math.random() * 1000000) + 100000
}));
} else {
// 未启用测试数据源,返回友好的错误提示
throw new Error('获取市场热点失败,请检查数据源配置');
// 无可用数据源
throw new Error('无可用数据源,请在管理配置中启用至少一个数据源');
}
} catch (error) {
console.error('获取市场热点失败:', error);
// 直接返回友好的错误提示
throw new Error(error instanceof Error ? error.message : '获取市场热点失败,请检查数据源配置');
}
};

@ -3,44 +3,65 @@
// 期货品种列表
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: 'AU', name: '黄金', type: '金属', exchange: 'SHFE' },
{ code: 'AG', name: '白银', type: '金属', exchange: 'SHFE' },
{ code: 'CU', name: '铜', type: '金属', exchange: 'SHFE' },
{ code: 'NI', name: '镍', type: '金属', exchange: 'SHFE' },
{ code: 'SN', name: '锡', type: '金属', exchange: 'SHFE' },
{ code: 'AL', name: '铝', type: '金属', exchange: 'SHFE' },
{ code: 'ZN', name: '锌', type: '金属', exchange: 'SHFE' },
{ code: 'PB', name: '铅', type: '金属', exchange: 'SHFE' },
// 建材类
{ 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: 'FG', name: '玻璃', type: '建材', exchange: 'CZCE' },
{ code: 'LY', name: '烧碱', type: '建材', exchange: 'CZCE' },
{ code: 'SA', name: '纯碱', type: '建材', exchange: 'CZCE' },
{ code: 'JM', name: '焦煤', type: '建材', exchange: 'DCE' },
{ code: 'RB', name: '螺纹钢', type: '建材', exchange: 'SHFE' },
{ code: 'ALO', name: '氧化铝', type: '建材', exchange: 'SHFE' },
{ code: 'HC', name: '热轧卷板', type: '建材', exchange: 'SHFE' },
// 能源化工类
{ 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: 'MA', name: '甲醇', type: '能源化工', exchange: 'CZCE' },
{ code: 'V', name: 'PVC', type: '能源化工', exchange: 'DCE' },
{ code: 'FU', name: '燃油', type: '能源化工', exchange: 'SHFE' },
{ code: 'SC', name: '原油', type: '能源化工', exchange: 'INE' },
{ code: 'RU', name: '橡胶', type: '能源化工', exchange: 'SHFE' },
{ code: 'BR', name: '合成橡胶', type: '能源化工', exchange: 'DCE' },
{ code: 'NR', name: '20号胶', type: '能源化工', exchange: 'SHFE' },
{ code: 'BU', name: '沥青', type: '能源化工', exchange: 'SHFE' },
{ code: 'LU', name: '低硫燃油', type: '能源化工', exchange: 'INE' },
{ code: 'L', name: '聚乙烯', type: '能源化工', exchange: 'DCE' },
{ code: 'PP', name: '聚丙烯', type: '能源化工', exchange: 'DCE' },
{ code: 'TA', name: 'PTA', type: '能源化工', exchange: 'CZCE' },
// 农产品类
{ code: 'P', name: '棕榈油', type: '农产品' },
{ code: 'P', name: '棕榈油', type: '农产品', exchange: 'DCE' },
{ code: 'A', name: '大豆', type: '农产品', exchange: 'DCE' },
{ code: 'B', name: '豆粕', type: '农产品', exchange: 'DCE' },
{ code: 'M', name: '豆粕', type: '农产品', exchange: 'DCE' },
{ code: 'Y', name: '豆油', type: '农产品', exchange: 'DCE' },
{ code: 'C', name: '玉米', type: '农产品', exchange: 'DCE' },
{ code: 'CS', name: '玉米淀粉', type: '农产品', exchange: 'DCE' },
{ code: 'CF', name: '棉花', type: '农产品', exchange: 'CZCE' },
{ code: 'SR', name: '白糖', type: '农产品', exchange: 'CZCE' },
{ code: 'RM', name: '菜籽粕', type: '农产品', exchange: 'CZCE' },
{ code: 'OI', name: '菜籽油', type: '农产品', exchange: 'CZCE' },
// 新能源类
{ code: 'LC', name: '碳酸锂', type: '新能源' },
{ code: 'SI', name: '工业硅', type: '新能源' },
{ code: 'PGS', name: '多晶硅', type: '新能源' },
{ code: 'LI', name: '碳酸锂', type: '新能源', exchange: 'SHFE' },
{ code: 'SI', name: '工业硅', type: '新能源', exchange: 'GEM' },
{ code: 'SP', name: '多晶硅', type: '新能源', exchange: 'GEM' },
// 金融类
{ code: 'IC', name: '中证500', type: '金融' },
{ code: 'IM', name: '中证1000', type: '金融' },
{ code: 'IH', name: '上证50', type: '金融' }
{ code: 'IM', name: '中证1000', type: '金融', exchange: 'CFFEX' },
{ code: 'IC', name: '中证500', type: '金融', exchange: 'CFFEX' },
{ code: 'IH', name: '上证50', type: '金融', exchange: 'CFFEX' },
// 其他
{ code: 'I', name: '铁矿石', type: '其他', exchange: 'DCE' },
{ code: 'J', name: '焦炭', type: '其他', exchange: 'DCE' },
{ code: 'ZC', name: '动力煤', type: '其他', exchange: 'CZCE' }
];
// 生成随机数据的工具函数

@ -1,6 +1,7 @@
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { App as AntdApp } from 'antd';
import store from './store';
import MainLayout from './components/layout/MainLayout';
// 使
@ -14,22 +15,24 @@ import './App.css';
function App() {
return (
<Provider store={store}>
<Router>
<MainLayout>
<Suspense fallback={<div className="loading-container">加载中...</div>}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/watchlist" element={<Watchlist />} />
<Route path="/detail/:code" element={<Detail />} />
<Route path="/risk-control" element={<RiskControl />} />
<Route path="/config" element={<Config />} />
<Route path="/admin" element={<AdminConfig />} />
</Routes>
</Suspense>
</MainLayout>
</Router>
</Provider>
<AntdApp>
<Provider store={store}>
<Router>
<MainLayout>
<Suspense fallback={<div className="loading-container">加载中...</div>}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/watchlist" element={<Watchlist />} />
<Route path="/detail/:code" element={<Detail />} />
<Route path="/risk-control" element={<RiskControl />} />
<Route path="/config" element={<Config />} />
<Route path="/admin" element={<AdminConfig />} />
</Routes>
</Suspense>
</MainLayout>
</Router>
</Provider>
</AntdApp>
);
}

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs, message } from 'antd';
import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs } from 'antd';
import { message } from 'antd';
import { DatabaseOutlined, KeyOutlined, SettingOutlined, SaveOutlined, ToolOutlined } from '@ant-design/icons';
import './AdminConfig.css';
@ -9,6 +10,12 @@ const { TabPane } = Tabs;
const AdminConfig = () => {
const [form] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage();
//
useEffect(() => {
fetchConfig();
}, []);
//
const databaseConfig = {
@ -163,10 +170,44 @@ const AdminConfig = () => {
}));
};
//
const fetchConfig = async () => {
try {
const response = await fetch('http://localhost:3007/api/config/get');
const result = await response.json();
if (result.success) {
const newConfig = result.data;
setConfig(newConfig);
//
form.setFieldsValue({
database: newConfig.database,
server: newConfig.server,
security: {
...newConfig.security,
cors: {
...newConfig.security.cors,
methods: newConfig.security.cors.methods.join(', '),
allowedHeaders: newConfig.security.cors.allowedHeaders.join(', ')
}
},
dataSource: newConfig.dataSource
});
messageApi.success('配置加载成功');
} else {
messageApi.error('配置加载失败');
}
} catch (error) {
console.error('获取配置失败:', error);
messageApi.error('获取配置失败,请检查网络连接');
}
};
//
const handleSubmit = async (values) => {
try {
const response = await fetch('http://localhost:3005/api/config/save', {
const response = await fetch('http://localhost:3007/api/config/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -176,20 +217,22 @@ const AdminConfig = () => {
const result = await response.json();
if (result.success) {
message.success(result.message);
messageApi.success(result.message);
//
fetchConfig();
} else {
message.error(result.message);
messageApi.error(result.message);
}
} catch (error) {
console.error('保存配置失败:', error);
message.error('保存配置失败,请检查网络连接');
messageApi.error('保存配置失败,请检查网络连接');
}
};
//
const testDatabaseConnection = async (dbType) => {
try {
const response = await fetch('http://localhost:3005/api/config/test-database', {
const response = await fetch('http://localhost:3007/api/config/test-database', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -202,20 +245,20 @@ const AdminConfig = () => {
const result = await response.json();
if (result.success) {
message.success(result.message);
messageApi.success(result.message);
} else {
message.error(result.message);
messageApi.error(result.message);
}
} catch (error) {
console.error('测试数据库连接失败:', error);
message.error('测试数据库连接失败,请检查网络连接');
messageApi.error('测试数据库连接失败,请检查网络连接');
}
};
//
const testDataSourceConnection = async (dsType) => {
try {
const response = await fetch('http://localhost:3005/api/config/test-datasource', {
const response = await fetch('http://localhost:3007/api/config/test-datasource', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -228,18 +271,20 @@ const AdminConfig = () => {
const result = await response.json();
if (result.success) {
message.success(result.message);
messageApi.success(result.message);
} else {
message.error(result.message);
messageApi.error(result.message);
}
} catch (error) {
console.error('测试数据源连接失败:', error);
message.error('测试数据源连接失败,请检查网络连接');
messageApi.error('测试数据源连接失败,请检查网络连接');
}
};
return (
<div className="admin-config">
<>
{contextHolder}
<div className="admin-config">
<h2>管理配置</h2>
<Alert
title="警告:此页面包含敏感配置信息,请谨慎操作"
@ -1183,7 +1228,8 @@ const AdminConfig = () => {
</Button>
</div>
</Form>
</div>
</div>
</>
);
};

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Row, Col, Statistic, Button, Select, Tag, message, Spin, Alert, Modal, Form, InputNumber, Switch, Checkbox } from 'antd';
import { Card, Row, Col, Statistic, Button, Select, Tag, Spin, Alert, Modal, Form, InputNumber, Switch, Checkbox } from 'antd';
import { message } from 'antd';
import { ReloadOutlined, ArrowUpOutlined, ArrowDownOutlined, FireOutlined, AlertOutlined, RobotOutlined, BellOutlined } from '@ant-design/icons';
import { fetchFuturesOverview, fetchRiskAlerts, fetchAIMarketAnalysis, toggleWatchlist } from '../../store/futuresSlice';
import { useNavigate } from 'react-router-dom';
@ -18,6 +19,7 @@ const Dashboard = () => {
const [pushModalVisible, setPushModalVisible] = useState(false);
const [currentFuture, setCurrentFuture] = useState(null);
const [pushForm] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage();
useEffect(() => {
dispatch(fetchFuturesOverview());
@ -29,7 +31,7 @@ const Dashboard = () => {
dispatch(fetchFuturesOverview());
dispatch(fetchRiskAlerts());
dispatch(fetchAIMarketAnalysis());
message.success('数据已刷新');
messageApi.success('数据已刷新');
};
const handleFutureClick = (future) => {
@ -39,7 +41,7 @@ const Dashboard = () => {
const handleToggleWatchlist = (future, e) => {
e.stopPropagation();
dispatch(toggleWatchlist(future.code));
message.success(future.isInWatchlist ? '已从自选移除' : '已添加到自选');
messageApi.success(future.isInWatchlist ? '已从自选移除' : '已添加到自选');
};
const openPushConfig = (future, e) => {
@ -52,7 +54,7 @@ const Dashboard = () => {
console.log('消息推送配置保存:', values);
//
setPushModalVisible(false);
message.success('消息推送配置已保存');
messageApi.success('消息推送配置已保存');
};
const getChangeColor = (changePercent) => {
@ -110,7 +112,9 @@ const Dashboard = () => {
}
return (
<div className="dashboard">
<>
{contextHolder}
<div className="dashboard">
{/* 页面头部 */}
<div className="dashboard-header">
<h2>市场概览</h2>
@ -469,7 +473,8 @@ const Dashboard = () => {
</Card>
</Form>
</Modal>
</div>
</div>
</>
);
};

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Row, Col, Button, Select, Tag, message, Spin, Modal, Form, InputNumber, Switch } from 'antd';
import { Card, Row, Col, Button, Select, Tag, Spin, Modal, Form, InputNumber, Switch } from 'antd';
import { message } from 'antd';
import { ReloadOutlined, ArrowUpOutlined, ArrowDownOutlined, StarOutlined, DeleteOutlined, BellOutlined } from '@ant-design/icons';
import { fetchFuturesOverview, toggleWatchlist } from '../../store/futuresSlice';
import { useNavigate } from 'react-router-dom';
@ -17,6 +18,7 @@ const Watchlist = () => {
const [pushModalVisible, setPushModalVisible] = useState(false);
const [currentFuture, setCurrentFuture] = useState(null);
const [pushForm] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage();
useEffect(() => {
dispatch(fetchFuturesOverview());
@ -24,7 +26,7 @@ const Watchlist = () => {
const handleRefresh = () => {
dispatch(fetchFuturesOverview());
message.success('数据已刷新');
messageApi.success('数据已刷新');
};
const handleFutureClick = (future) => {
@ -33,7 +35,7 @@ const Watchlist = () => {
const handleToggleWatchlist = (future) => {
dispatch(toggleWatchlist(future.code));
message.success(future.isInWatchlist ? '已从自选移除' : '已添加到自选');
messageApi.success(future.isInWatchlist ? '已从自选移除' : '已添加到自选');
};
const openPushConfig = (future, e) => {
@ -46,7 +48,7 @@ const Watchlist = () => {
console.log('消息推送配置保存:', values);
//
setPushModalVisible(false);
message.success('消息推送配置已保存');
messageApi.success('消息推送配置已保存');
};
const getChangeColor = (changePercent) => {
@ -78,7 +80,9 @@ const Watchlist = () => {
const watchlistData = getWatchlistData();
return (
<div className="watchlist">
<>
{contextHolder}
<div className="watchlist">
{/* 页面头部 */}
<div className="watchlist-header">
<h2>自选合约</h2>
@ -351,7 +355,8 @@ const Watchlist = () => {
</Card>
</Form>
</Modal>
</div>
</div>
</>
);
};

@ -3,44 +3,65 @@
// 期货品种列表
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: 'AU', name: '黄金', type: '金属', exchange: 'SHFE' },
{ code: 'AG', name: '白银', type: '金属', exchange: 'SHFE' },
{ code: 'CU', name: '铜', type: '金属', exchange: 'SHFE' },
{ code: 'NI', name: '镍', type: '金属', exchange: 'SHFE' },
{ code: 'SN', name: '锡', type: '金属', exchange: 'SHFE' },
{ code: 'AL', name: '铝', type: '金属', exchange: 'SHFE' },
{ code: 'ZN', name: '锌', type: '金属', exchange: 'SHFE' },
{ code: 'PB', name: '铅', type: '金属', exchange: 'SHFE' },
// 建材类
{ 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: 'FG', name: '玻璃', type: '建材', exchange: 'CZCE' },
{ code: 'LY', name: '烧碱', type: '建材', exchange: 'CZCE' },
{ code: 'SA', name: '纯碱', type: '建材', exchange: 'CZCE' },
{ code: 'JM', name: '焦煤', type: '建材', exchange: 'DCE' },
{ code: 'RB', name: '螺纹钢', type: '建材', exchange: 'SHFE' },
{ code: 'ALO', name: '氧化铝', type: '建材', exchange: 'SHFE' },
{ code: 'HC', name: '热轧卷板', type: '建材', exchange: 'SHFE' },
// 能源化工类
{ 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: 'MA', name: '甲醇', type: '能源化工', exchange: 'CZCE' },
{ code: 'V', name: 'PVC', type: '能源化工', exchange: 'DCE' },
{ code: 'FU', name: '燃油', type: '能源化工', exchange: 'SHFE' },
{ code: 'SC', name: '原油', type: '能源化工', exchange: 'INE' },
{ code: 'RU', name: '橡胶', type: '能源化工', exchange: 'SHFE' },
{ code: 'BR', name: '合成橡胶', type: '能源化工', exchange: 'DCE' },
{ code: 'NR', name: '20号胶', type: '能源化工', exchange: 'SHFE' },
{ code: 'BU', name: '沥青', type: '能源化工', exchange: 'SHFE' },
{ code: 'LU', name: '低硫燃油', type: '能源化工', exchange: 'INE' },
{ code: 'L', name: '聚乙烯', type: '能源化工', exchange: 'DCE' },
{ code: 'PP', name: '聚丙烯', type: '能源化工', exchange: 'DCE' },
{ code: 'TA', name: 'PTA', type: '能源化工', exchange: 'CZCE' },
// 农产品类
{ code: 'P', name: '棕榈油', type: '农产品' },
{ code: 'P', name: '棕榈油', type: '农产品', exchange: 'DCE' },
{ code: 'A', name: '大豆', type: '农产品', exchange: 'DCE' },
{ code: 'B', name: '豆粕', type: '农产品', exchange: 'DCE' },
{ code: 'M', name: '豆粕', type: '农产品', exchange: 'DCE' },
{ code: 'Y', name: '豆油', type: '农产品', exchange: 'DCE' },
{ code: 'C', name: '玉米', type: '农产品', exchange: 'DCE' },
{ code: 'CS', name: '玉米淀粉', type: '农产品', exchange: 'DCE' },
{ code: 'CF', name: '棉花', type: '农产品', exchange: 'CZCE' },
{ code: 'SR', name: '白糖', type: '农产品', exchange: 'CZCE' },
{ code: 'RM', name: '菜籽粕', type: '农产品', exchange: 'CZCE' },
{ code: 'OI', name: '菜籽油', type: '农产品', exchange: 'CZCE' },
// 新能源类
{ code: 'LC', name: '碳酸锂', type: '新能源' },
{ code: 'SI', name: '工业硅', type: '新能源' },
{ code: 'PGS', name: '多晶硅', type: '新能源' },
{ code: 'LI', name: '碳酸锂', type: '新能源', exchange: 'SHFE' },
{ code: 'SI', name: '工业硅', type: '新能源', exchange: 'GEM' },
{ code: 'SP', name: '多晶硅', type: '新能源', exchange: 'GEM' },
// 金融类
{ code: 'IC', name: '中证500', type: '金融' },
{ code: 'IM', name: '中证1000', type: '金融' },
{ code: 'IH', name: '上证50', type: '金融' }
{ code: 'IM', name: '中证1000', type: '金融', exchange: 'CFFEX' },
{ code: 'IC', name: '中证500', type: '金融', exchange: 'CFFEX' },
{ code: 'IH', name: '上证50', type: '金融', exchange: 'CFFEX' },
// 其他
{ code: 'I', name: '铁矿石', type: '其他', exchange: 'DCE' },
{ code: 'J', name: '焦炭', type: '其他', exchange: 'DCE' },
{ code: 'ZC', name: '动力煤', type: '其他', exchange: 'CZCE' }
];
// 生成随机数据的工具函数

Loading…
Cancel
Save