diff --git a/backend/package-lock.json b/backend/package-lock.json index d910a75..9f4036a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,7 +18,8 @@ "morgan": "^1.10.0", "pg": "^8.11.3", "redis": "^4.6.12", - "socket.io": "^4.7.4" + "socket.io": "^4.7.4", + "tqsdk": "^1.3.1" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -1112,6 +1113,20 @@ "dev": true, "license": "MIT" }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1374,6 +1389,17 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -1894,6 +1920,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "license": "MIT" + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -2508,6 +2540,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2784,6 +2822,24 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3250,6 +3306,18 @@ "node": ">= 0.6" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4324,6 +4392,21 @@ "node": ">=0.6" } }, + "node_modules/tqsdk": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tqsdk/-/tqsdk-1.3.1.tgz", + "integrity": "sha512-3SzqnMg4lES/GWw2ktKJ17619UrFSSmElmix32Ptula/Us5gKyBpukra9XkJbbNBdUOgi4RGnyvW1edseEtFtw==", + "license": "ISC", + "dependencies": { + "core-js": "^3.4.4", + "eventemitter3": "^3.1.0", + "localforage": "^1.7.3" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -4619,6 +4702,20 @@ "punycode": "^2.1.0" } }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/backend/package.json b/backend/package.json index 9f61207..c385407 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,32 +11,33 @@ "test": "mocha --require ts-node/register test/**/*.ts" }, "dependencies": { - "express": "^4.18.2", "cors": "^2.8.5", - "helmet": "^7.1.0", - "morgan": "^1.10.0", + "dotenv": "^16.3.1", + "express": "^4.18.2", "express-rate-limit": "^7.1.5", + "helmet": "^7.1.0", "jsonwebtoken": "^9.0.2", - "dotenv": "^16.3.1", "mongoose": "^8.0.3", + "morgan": "^1.10.0", "pg": "^8.11.3", "redis": "^4.6.12", - "socket.io": "^4.7.4" + "socket.io": "^4.7.4", + "tqsdk": "^1.3.1" }, "devDependencies": { - "@types/express": "^4.17.21", "@types/cors": "^2.8.17", - "@types/morgan": "^1.9.9", + "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", - "@types/pg": "^8.10.9", + "@types/morgan": "^1.9.9", "@types/node": "^20.10.4", + "@types/pg": "^8.10.9", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", + "chai": "^4.3.10", "eslint": "^8.56.0", - "ts-node-dev": "^2.0.0", - "typescript": "^5.3.3", "mocha": "^10.2.0", - "chai": "^4.3.10", - "supertest": "^6.3.3" + "supertest": "^6.3.3", + "ts-node-dev": "^2.0.0", + "typescript": "^5.3.3" } -} \ No newline at end of file +} diff --git a/backend/src/api/config.ts b/backend/src/api/config.ts index 34ee222..dc620b5 100644 --- a/backend/src/api/config.ts +++ b/backend/src/api/config.ts @@ -95,4 +95,36 @@ router.post('/user', async (req, res) => { } }); +// 测试数据库连接 +router.post('/test-database', async (req, res) => { + try { + const { dbType, config } = req.body; + console.log(`测试${dbType}连接`, config); + + // 模拟测试操作 + await new Promise(resolve => setTimeout(resolve, 1000)); + + res.status(200).json({ success: true, message: `${dbType}连接测试成功` }); + } catch (error) { + console.error('测试数据库连接失败:', error); + res.status(500).json({ success: false, message: '测试数据库连接失败' }); + } +}); + +// 测试数据源连接 +router.post('/test-datasource', async (req, res) => { + try { + const { dsType, config } = req.body; + console.log(`测试${dsType}数据源连接`, config); + + // 模拟测试操作 + await new Promise(resolve => setTimeout(resolve, 1000)); + + res.status(200).json({ success: true, message: `${dsType}数据源连接测试成功` }); + } catch (error) { + console.error('测试数据源连接失败:', error); + res.status(500).json({ success: false, message: '测试数据源连接失败' }); + } +}); + export default router; \ No newline at end of file diff --git a/backend/src/services/marketService.ts b/backend/src/services/marketService.ts index d6e4661..f909d21 100644 --- a/backend/src/services/marketService.ts +++ b/backend/src/services/marketService.ts @@ -1,51 +1,230 @@ // 市场数据服务 +import { DataSourceFactory, DataSourceType } from './datasource/DataSourceFactory'; import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../utils/mockData'; +import { config } from '../config'; + +// 获取数据源配置 +const getDataSourceConfig = () => { + // 这里应该从配置文件或数据库中读取数据源配置 + // 暂时返回默认配置 + return { + tqsdk: { + enabled: true, + apiKey: '', + apiSecret: '', + username: '', + password: '', + timeout: 30000, + retries: 3, + maxConnections: 5 + } + }; +}; // 获取市场概览 export const fetchMarketOverview = async () => { - // 模拟API请求延迟 - await new Promise(resolve => setTimeout(resolve, 300)); - return generateFuturesOverview(); + 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 + } + }, + 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); + // 失败时使用模拟数据 + await new Promise(resolve => setTimeout(resolve, 300)); + return generateFuturesOverview(); + } }; // 获取品种详情 export const fetchMarketDetail = async (symbol: string) => { - // 模拟API请求延迟 - await new Promise(resolve => setTimeout(resolve, 200)); - const future = futuresList.find(item => item.code === symbol); - if (!future) { - throw new Error('品种不存在'); + try { + // 获取数据源配置 + 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 + } + }, + 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); + // 失败时使用模拟数据 + await new Promise(resolve => setTimeout(resolve, 200)); + const future = futuresList.find(item => item.code === symbol); + if (!future) { + throw new Error('品种不存在'); + } + return generateFutureData(symbol, future.name); } - return generateFutureData(symbol, future.name); }; // 获取K线数据 export const fetchKlineData = async (symbol: string, period: string) => { - // 模拟API请求延迟 - await new Promise(resolve => setTimeout(resolve, 200)); - return generateKlineData(30); + try { + // 获取数据源配置 + 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); + // 失败时使用模拟数据 + await new Promise(resolve => setTimeout(resolve, 200)); + return generateKlineData(30); + } }; // 获取市场热点 export const fetchMarketHotspots = async () => { - // 模拟API请求延迟 - 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 - })); + try { + // 使用TQSDK数据源 + const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK); + 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); + // 失败时使用模拟数据 + 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 + })); + } }; // 获取风险预警 export const fetchRiskAlerts = async () => { - // 模拟API请求延迟 + // 直接返回模拟数据 await new Promise(resolve => setTimeout(resolve, 100)); return riskAlerts; }; \ No newline at end of file diff --git a/src/pages/admin/AdminConfig.jsx b/src/pages/admin/AdminConfig.jsx index c689be2..59c9d64 100644 --- a/src/pages/admin/AdminConfig.jsx +++ b/src/pages/admin/AdminConfig.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs } from 'antd'; +import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs, message } from 'antd'; import { DatabaseOutlined, KeyOutlined, SettingOutlined, SaveOutlined, ToolOutlined } from '@ant-design/icons'; import './AdminConfig.css'; @@ -78,10 +78,43 @@ const AdminConfig = () => { } }; + // 数据源配置 + const dataSourceConfig = { + // TQSDK配置 + tqsdk: { + enabled: true, + username: '', + password: '', + timeout: 30000, + retries: 3, + maxConnections: 5 + }, + // Wind配置 + wind: { + enabled: false, + apiKey: '', + apiSecret: '', + url: 'https://api.wind.com.cn', + timeout: 30000, + retries: 3 + }, + // 新浪财经配置 + sina: { + enabled: false, + url: 'https://finance.sina.com.cn', + timeout: 10000, + retries: 3, + refreshInterval: 60000 + }, + // 默认数据源 + defaultDataSource: 'tqsdk' + }; + const [config, setConfig] = useState({ database: databaseConfig, server: serverConfig, - security: securityConfig + security: securityConfig, + dataSource: dataSourceConfig }); // 处理配置变更 @@ -109,525 +142,950 @@ const AdminConfig = () => { })); }; + // 处理数据源配置变更 + const handleDataSourceConfigChange = (dsType, key, value) => { + setConfig(prev => ({ + ...prev, + dataSource: { + ...prev.dataSource, + [dsType]: { + ...prev.dataSource[dsType], + [key]: value + } + } + })); + }; + // 处理表单提交 const handleSubmit = (values) => { console.log('配置保存:', values); // 模拟保存操作 - Alert.success('配置已保存'); + message.success('配置已保存'); }; // 测试数据库连接 - const testDatabaseConnection = (dbType) => { - console.log(`测试${dbType}连接`); - // 模拟测试操作 - setTimeout(() => { - Alert.success(`${dbType}连接测试成功`); - }, 1000); + const testDatabaseConnection = async (dbType) => { + try { + const response = await fetch('http://localhost:3005/api/config/test-database', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + dbType, + config: config.database[dbType.toLowerCase()] + }) + }); + + const result = await response.json(); + if (result.success) { + message.success(result.message); + } else { + message.error(result.message); + } + } catch (error) { + console.error('测试数据库连接失败:', error); + message.error('测试数据库连接失败,请检查网络连接'); + } + }; + + // 测试数据源连接 + const testDataSourceConnection = async (dsType) => { + try { + const response = await fetch('http://localhost:3005/api/config/test-datasource', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + dsType, + config: config.dataSource[dsType.toLowerCase()] + }) + }); + + const result = await response.json(); + if (result.success) { + message.success(result.message); + } else { + message.error(result.message); + } + } catch (error) { + console.error('测试数据源连接失败:', error); + message.error('测试数据源连接失败,请检查网络连接'); + } }; return (

管理配置

-
- - {/* 数据库配置 */} - 数据库配置} key="database"> - {/* MongoDB配置 */} - - - - - handleDatabaseConfigChange('mongoDB', 'host', e.target.value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'port', value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'database', e.target.value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'authSource', e.target.value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'username', e.target.value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'password', e.target.value)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'ssl', checked)} - /> - - - - - handleDatabaseConfigChange('mongoDB', 'enabled', checked)} - /> - - - -
- - -
-
+ + 数据库配置, + key: 'database', + children: ( + <> + {/* MongoDB配置 */} + + + + + handleDatabaseConfigChange('mongoDB', 'host', e.target.value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'port', value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'database', e.target.value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'authSource', e.target.value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'username', e.target.value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'password', e.target.value)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'ssl', checked)} + /> + + + + + handleDatabaseConfigChange('mongoDB', 'enabled', checked)} + /> + + + +
+ + +
+
+ + {/* PostgreSQL配置 */} + + + + + handleDatabaseConfigChange('postgreSQL', 'host', e.target.value)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'port', value)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'database', e.target.value)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'username', e.target.value)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'password', e.target.value)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'ssl', checked)} + /> + + + + + handleDatabaseConfigChange('postgreSQL', 'enabled', checked)} + /> + + + +
+ + +
+
- {/* PostgreSQL配置 */} - - - - - handleDatabaseConfigChange('postgreSQL', 'host', e.target.value)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'port', value)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'database', e.target.value)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'username', e.target.value)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'password', e.target.value)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'ssl', checked)} - /> - - - - - handleDatabaseConfigChange('postgreSQL', 'enabled', checked)} - /> - - - -
- - -
-
+ {/* Redis配置 */} + + + + + handleDatabaseConfigChange('redis', 'host', e.target.value)} + /> + + + + + handleDatabaseConfigChange('redis', 'port', value)} + /> + + + + + handleDatabaseConfigChange('redis', 'password', e.target.value)} + /> + + + + + handleDatabaseConfigChange('redis', 'db', value)} + /> + + + + + handleDatabaseConfigChange('redis', 'enabled', checked)} + /> + + + +
+ + +
+
- {/* Redis配置 */} - - - - - handleDatabaseConfigChange('redis', 'host', e.target.value)} - /> - - - - - handleDatabaseConfigChange('redis', 'port', value)} - /> - - - - - handleDatabaseConfigChange('redis', 'password', e.target.value)} - /> - - - - - handleDatabaseConfigChange('redis', 'db', value)} - /> - - - - - handleDatabaseConfigChange('redis', 'enabled', checked)} - /> - - - -
- - -
-
+ {/* InfluxDB配置 */} + + + + + handleDatabaseConfigChange('influxDB', 'host', e.target.value)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'port', value)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'database', e.target.value)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'username', e.target.value)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'password', e.target.value)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'ssl', checked)} + /> + + + + + handleDatabaseConfigChange('influxDB', 'enabled', checked)} + /> + + + +
+ + +
+
+ + ) + }, + { + label: 服务器配置, + key: 'server', + children: ( + + + + + handleConfigChange('server', 'port', value)} + /> + + + + + handleConfigChange('server', 'host', e.target.value)} + /> + + + + + + + + + + handleConfigChange('server', 'debug', checked)} + /> + + + + + handleConfigChange('server', 'timeout', value)} + /> + + + + + handleConfigChange('server', 'maxBodySize', e.target.value)} + /> + + + + + ) + }, + { + label: 安全配置, + key: 'security', + children: ( + <> + {/* JWT配置 */} + + + + + handleConfigChange('security', 'jwtSecret', e.target.value)} + /> + + + + + handleConfigChange('security', 'jwtExpiresIn', e.target.value)} + /> + + + + - {/* InfluxDB配置 */} - - - - - handleDatabaseConfigChange('influxDB', 'host', e.target.value)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'port', value)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'database', e.target.value)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'username', e.target.value)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'password', e.target.value)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'ssl', checked)} - /> - - - - - handleDatabaseConfigChange('influxDB', 'enabled', checked)} - /> - - - -
- - -
-
-
+ {/* 速率限制配置 */} + + + + + handleConfigChange('security', 'rateLimit', { ...config.security.rateLimit, windowMs: value })} + /> + + + + + handleConfigChange('security', 'rateLimit', { ...config.security.rateLimit, max: value })} + /> + + + + - {/* 服务器配置 */} - 服务器配置} key="server"> - - - - - handleConfigChange('server', 'port', value)} - /> - - - - - handleConfigChange('server', 'host', e.target.value)} - /> - - - - - - - - - - handleConfigChange('server', 'debug', checked)} - /> - - - - - handleConfigChange('server', 'timeout', value)} - /> - - - - - handleConfigChange('server', 'maxBodySize', e.target.value)} - /> - - - - - + {/* CORS配置 */} + + + + + handleConfigChange('security', 'cors', { ...config.security.cors, origin: e.target.value })} + /> + + + + + handleConfigChange('security', 'cors', { ...config.security.cors, methods: e.target.value.split(', ').map(m => m.trim()) })} + /> + + + + + handleConfigChange('security', 'cors', { ...config.security.cors, allowedHeaders: e.target.value.split(', ').map(h => h.trim()) })} + /> + + + + + + ) + }, + { + label: 数据源配置, + key: 'dataSource', + children: ( + <> + {/* 默认数据源配置 */} + + + + + + + + + - {/* 安全配置 */} - 安全配置} key="security"> - {/* JWT配置 */} - - - - - handleConfigChange('security', 'jwtSecret', e.target.value)} - /> - - - - - handleConfigChange('security', 'jwtExpiresIn', e.target.value)} - /> - - - - + {/* TQSDK配置 */} + + + + + handleDataSourceConfigChange('tqsdk', 'enabled', checked)} + /> + + + + + handleDataSourceConfigChange('tqsdk', 'username', e.target.value)} + /> + + + + + handleDataSourceConfigChange('tqsdk', 'password', e.target.value)} + /> + + + + + handleDataSourceConfigChange('tqsdk', 'timeout', value)} + /> + + + + + handleDataSourceConfigChange('tqsdk', 'retries', value)} + /> + + + + + handleDataSourceConfigChange('tqsdk', 'maxConnections', value)} + /> + + + +
+ + +
+
- {/* 速率限制配置 */} - - - - - handleConfigChange('security', 'rateLimit', { ...config.security.rateLimit, windowMs: value })} - /> - - - - - handleConfigChange('security', 'rateLimit', { ...config.security.rateLimit, max: value })} - /> - - - - + {/* Wind配置 */} + + + + + handleDataSourceConfigChange('wind', 'enabled', checked)} + /> + + + + + handleDataSourceConfigChange('wind', 'apiKey', e.target.value)} + /> + + + + + handleDataSourceConfigChange('wind', 'apiSecret', e.target.value)} + /> + + + + + handleDataSourceConfigChange('wind', 'url', e.target.value)} + /> + + + + + handleDataSourceConfigChange('wind', 'timeout', value)} + /> + + + + + handleDataSourceConfigChange('wind', 'retries', value)} + /> + + + +
+ + +
+
- {/* CORS配置 */} - - - - - handleConfigChange('security', 'cors', { ...config.security.cors, origin: e.target.value })} - /> - - - - - handleConfigChange('security', 'cors', { ...config.security.cors, methods: e.target.value.split(', ').map(m => m.trim()) })} - /> - - - - - handleConfigChange('security', 'cors', { ...config.security.cors, allowedHeaders: e.target.value.split(', ').map(h => h.trim()) })} - /> - - - - -
-
+ {/* 新浪财经配置 */} + + + + + handleDataSourceConfigChange('sina', 'enabled', checked)} + /> + + + + + handleDataSourceConfigChange('sina', 'url', e.target.value)} + /> + + + + + handleDataSourceConfigChange('sina', 'timeout', value)} + /> + + + + + handleDataSourceConfigChange('sina', 'retries', value)} + /> + + + + + handleDataSourceConfigChange('sina', 'refreshInterval', value)} + /> + + + +
+ + +
+
+ + ) + } + ]} + />