diff --git a/frontend/index.html b/frontend/index.html index 3612154..bc6a0c2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + 自动交易系统 diff --git a/frontend/src/App.css b/frontend/src/App.css index afa1880..11c9fa8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -12,28 +12,64 @@ .nav-container { max-width: 1200px; margin: 0 auto; - padding: 0 2rem; + padding: 0 1rem; display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + gap: 0.75rem; + align-items: flex-start; +} + +@media (min-width: 768px) { + .nav-container { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 2rem; + gap: 0; + } } .nav-title { - font-size: 1.5rem; + font-size: 1.25rem; font-weight: bold; } +@media (min-width: 768px) { + .nav-title { + font-size: 1.5rem; + } +} + .nav-links { display: flex; - gap: 2rem; + flex-wrap: wrap; + gap: 0.5rem; + width: 100%; +} + +@media (min-width: 768px) { + .nav-links { + width: auto; + gap: 2rem; + } } .nav-links a { color: white; text-decoration: none; - padding: 0.5rem 1rem; + padding: 0.5rem 0.75rem; border-radius: 4px; transition: background-color 0.3s; + font-size: 0.9rem; + display: block; + touch-action: manipulation; +} + +@media (min-width: 768px) { + .nav-links a { + padding: 0.5rem 1rem; + font-size: 1rem; + } } .nav-links a:hover { @@ -42,6 +78,13 @@ .main-content { max-width: 1200px; - margin: 2rem auto; - padding: 0 2rem; + margin: 1rem auto; + padding: 0 0.5rem; +} + +@media (min-width: 768px) { + .main-content { + margin: 2rem auto; + padding: 0 2rem; + } } diff --git a/frontend/src/components/ConfigPanel.css b/frontend/src/components/ConfigPanel.css index bb3715e..87faae8 100644 --- a/frontend/src/components/ConfigPanel.css +++ b/frontend/src/components/ConfigPanel.css @@ -1,21 +1,36 @@ .config-panel { background: white; - padding: 2rem; + padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } +@media (min-width: 768px) { + .config-panel { + padding: 2rem; + } +} + .config-header { margin-bottom: 2rem; } .header-top { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + gap: 0.75rem; margin-bottom: 1rem; } +@media (min-width: 768px) { + .header-top { + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 0; + } +} + .guide-link { color: #2196F3; text-decoration: none; @@ -33,18 +48,32 @@ .preset-section { background: #f8f9fa; - padding: 1.5rem; + padding: 1rem; border-radius: 8px; - margin-top: 1.5rem; + margin-top: 1rem; +} + +@media (min-width: 768px) { + .preset-section { + padding: 1.5rem; + margin-top: 1.5rem; + } } .preset-header { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + gap: 0.75rem; margin-bottom: 1rem; - flex-wrap: wrap; - gap: 1rem; +} + +@media (min-width: 768px) { + .preset-header { + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 1rem; + } } .preset-section h3 { @@ -87,13 +116,19 @@ .preset-buttons { display: flex; - gap: 1rem; - flex-wrap: wrap; + flex-direction: column; + gap: 0.75rem; +} + +@media (min-width: 768px) { + .preset-buttons { + flex-direction: row; + gap: 1rem; + } } .preset-btn { - flex: 1; - min-width: 200px; + width: 100%; padding: 1rem; border: 2px solid #dee2e6; background: white; @@ -101,6 +136,14 @@ cursor: pointer; transition: all 0.3s; text-align: left; + touch-action: manipulation; +} + +@media (min-width: 768px) { + .preset-btn { + flex: 1; + min-width: 200px; + } } .preset-btn:hover:not(:disabled) { @@ -213,14 +256,98 @@ .config-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-template-columns: 1fr; gap: 1rem; } +@media (min-width: 768px) { + .config-grid { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + } +} + .config-item { display: flex; flex-direction: column; gap: 0.5rem; + padding: 1rem; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.config-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.help-btn { + padding: 0.25rem 0.75rem; + font-size: 0.85rem; + background: #2196F3; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s; +} + +.help-btn:hover { + background: #1976D2; +} + +.help-btn:active { + transform: scale(0.95); +} + +.config-detail-popup { + background: white; + padding: 1rem; + border-radius: 6px; + border: 1px solid #dee2e6; + margin-bottom: 0.5rem; + font-size: 0.9rem; + line-height: 1.6; + color: #555; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.config-input-wrapper { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.config-input-wrapper input { + flex: 1; +} + +.save-btn { + padding: 0.5rem 1rem; + background: #4CAF50; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + transition: all 0.3s; + white-space: nowrap; +} + +.save-btn:hover:not(:disabled) { + background: #45a049; +} + +.save-btn:active:not(:disabled) { + transform: scale(0.95); +} + +.save-btn:disabled { + opacity: 0.6; + cursor: not-allowed; } .config-item label { @@ -231,11 +358,21 @@ .config-item input, .config-item select { - padding: 0.5rem; + padding: 0.75rem; border: 1px solid #ddd; - border-radius: 4px; + border-radius: 6px; font-size: 1rem; transition: border-color 0.3s, box-shadow 0.3s; + width: 100%; + -webkit-appearance: none; + appearance: none; +} + +@media (min-width: 768px) { + .config-item input, + .config-item select { + padding: 0.5rem; + } } .config-item input:focus, @@ -270,7 +407,7 @@ display: flex; align-items: center; gap: 0.25rem; - cursor: help; + margin-top: 0.25rem; } .help-icon { @@ -284,3 +421,36 @@ font-size: 1.2rem; color: #666; } + +/* 移动端优化 */ +@media (max-width: 767px) { + .config-item label { + font-size: 0.95rem; + } + + .config-section h3 { + font-size: 1.1rem; + } + + .preset-btn { + min-height: 60px; + } + + .guide-link { + width: 100%; + text-align: center; + padding: 0.75rem; + font-size: 0.95rem; + } + + .config-info { + font-size: 0.85rem; + padding: 0.75rem; + } + + /* 确保按钮有足够的触摸区域 */ + button, .help-btn, .save-btn { + min-height: 44px; + min-width: 44px; + } +} diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index b5fd422..a03c570 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -246,6 +246,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { const [value, setValue] = useState(config.value) const [localValue, setLocalValue] = useState(config.value) const [isEditing, setIsEditing] = useState(false) + const [showDetail, setShowDetail] = useState(false) useEffect(() => { setValue(config.value) @@ -257,7 +258,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { setIsEditing(true) } - const handleBlur = () => { + const handleSave = () => { setIsEditing(false) if (localValue !== value) { // 值发生变化,保存 @@ -266,7 +267,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { finalValue = parseFloat(localValue) || 0 // 百分比配置需要转换 if (label.includes('PERCENT')) { - finalValue = finalValue / 100 + finalValue = finalValue } } else if (config.type === 'boolean') { finalValue = localValue === 'true' || localValue === true @@ -275,9 +276,16 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { } } + const handleBlur = () => { + // 移动端不自动保存,需要点击保存按钮 + if (window.innerWidth > 768) { + handleSave() + } + } + const handleKeyPress = (e) => { if (e.key === 'Enter') { - handleBlur() + handleSave() } } @@ -288,7 +296,23 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { if (config.type === 'boolean') { return (
- +
+ + {config.description && ( + + )} +
+ {showDetail && ( +
+ {getConfigDetail(label)} +
+ )} {config.description && ( - + {config.description} - ℹ️ )}
@@ -315,7 +338,23 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { const options = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '1d'] return (
- +
+ + {config.description && ( + + )} +
+ {showDetail && ( +
+ {getConfigDetail(label)} +
+ )} {config.description && ( - + {config.description} - ℹ️ )}
@@ -341,27 +379,54 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { return (
- - { - const newValue = config.type === 'number' && label.includes('PERCENT') - ? parseFloat(e.target.value) / 100 - : e.target.value - handleChange(newValue) - }} - onBlur={handleBlur} - onKeyPress={handleKeyPress} - disabled={disabled} - step={config.type === 'number' ? '0.01' : undefined} - className={isEditing ? 'editing' : ''} - /> - {isEditing && 按Enter保存} +
+ + {config.description && ( + + )} +
+ {showDetail && ( +
+ {getConfigDetail(label)} +
+ )} +
+ { + const newValue = config.type === 'number' && label.includes('PERCENT') + ? parseFloat(e.target.value) / 100 + : e.target.value + handleChange(newValue) + }} + onBlur={handleBlur} + onKeyPress={handleKeyPress} + disabled={disabled} + step={config.type === 'number' ? '0.01' : undefined} + className={isEditing ? 'editing' : ''} + /> + {isEditing && ( + + )} +
+ {isEditing && window.innerWidth > 768 && 按Enter保存} {config.description && ( - + {config.description} - ℹ️ )}
diff --git a/frontend/src/components/StatsDashboard.css b/frontend/src/components/StatsDashboard.css index c36d607..9552585 100644 --- a/frontend/src/components/StatsDashboard.css +++ b/frontend/src/components/StatsDashboard.css @@ -1,27 +1,54 @@ .dashboard { background: white; - padding: 2rem; + padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } +@media (min-width: 768px) { + .dashboard { + padding: 2rem; + } +} + .dashboard h2 { - margin-bottom: 2rem; + margin-bottom: 1rem; color: #2c3e50; + font-size: 1.5rem; +} + +@media (min-width: 768px) { + .dashboard h2 { + margin-bottom: 2rem; + font-size: 2rem; + } } .dashboard-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 2rem; + grid-template-columns: 1fr; + gap: 1rem; +} + +@media (min-width: 768px) { + .dashboard-grid { + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 2rem; + } } .dashboard-card { background: #f8f9fa; - padding: 1.5rem; + padding: 1rem; border-radius: 8px; } +@media (min-width: 768px) { + .dashboard-card { + padding: 1.5rem; + } +} + .dashboard-card h3 { margin-bottom: 1rem; color: #34495e; @@ -36,10 +63,17 @@ .info-item { display: flex; justify-content: space-between; - padding: 0.5rem 0; + align-items: center; + padding: 0.75rem 0; border-bottom: 1px solid #dee2e6; } +@media (min-width: 768px) { + .info-item { + padding: 0.5rem 0; + } +} + .info-item:last-child { border-bottom: none; } @@ -47,11 +81,26 @@ .info-item .label { color: #666; font-weight: 500; + font-size: 0.9rem; +} + +@media (min-width: 768px) { + .info-item .label { + font-size: 1rem; + } } .info-item .value { font-weight: bold; color: #2c3e50; + font-size: 0.95rem; + text-align: right; +} + +@media (min-width: 768px) { + .info-item .value { + font-size: 1rem; + } } .info-item .value.positive { @@ -70,26 +119,52 @@ .trade-item { display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; + flex-direction: column; + padding: 1rem; background: white; - border-radius: 4px; - gap: 1rem; + border-radius: 8px; + gap: 0.75rem; + margin-bottom: 0.75rem; + border: 1px solid #e9ecef; +} + +@media (min-width: 768px) { + .trade-item { + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 0; + } } .trade-info { - display: flex; - flex-direction: column; - gap: 0.25rem; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.5rem; font-size: 0.85rem; color: #666; flex: 1; } +@media (min-width: 768px) { + .trade-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + } +} + .trade-symbol { font-weight: bold; color: #2c3e50; + font-size: 1.1rem; +} + +@media (min-width: 768px) { + .trade-symbol { + font-size: 1rem; + } } .trade-side { @@ -111,6 +186,19 @@ .trade-pnl { font-weight: bold; + font-size: 1rem; + text-align: right; + padding: 0.5rem; + background: #f8f9fa; + border-radius: 6px; +} + +@media (min-width: 768px) { + .trade-pnl { + background: transparent; + padding: 0; + text-align: left; + } } .loading { diff --git a/frontend/src/components/TradeList.css b/frontend/src/components/TradeList.css index 925357f..6572739 100644 --- a/frontend/src/components/TradeList.css +++ b/frontend/src/components/TradeList.css @@ -1,19 +1,47 @@ .trade-list { background: white; - padding: 2rem; + padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } +@media (min-width: 768px) { + .trade-list { + padding: 2rem; + } +} + +.trade-list h2 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +@media (min-width: 768px) { + .trade-list h2 { + font-size: 2rem; + margin-bottom: 2rem; + } +} + .filter-panel { background: #f8f9fa; - padding: 1.5rem; + padding: 1rem; border-radius: 8px; - margin-bottom: 2rem; + margin-bottom: 1rem; display: flex; - flex-wrap: wrap; - gap: 1.5rem; - align-items: flex-end; + flex-direction: column; + gap: 1rem; +} + +@media (min-width: 768px) { + .filter-panel { + padding: 1.5rem; + margin-bottom: 2rem; + flex-direction: row; + flex-wrap: wrap; + gap: 1.5rem; + align-items: flex-end; + } } .filter-section { @@ -32,19 +60,35 @@ } .period-buttons { - display: flex; + display: grid; + grid-template-columns: repeat(2, 1fr); gap: 0.5rem; - flex-wrap: wrap; +} + +@media (min-width: 768px) { + .period-buttons { + display: flex; + flex-wrap: wrap; + } } .period-buttons button { - padding: 0.5rem 1rem; + padding: 0.75rem 1rem; border: 1px solid #ddd; background: white; - border-radius: 4px; + border-radius: 6px; cursor: pointer; font-size: 0.9rem; transition: all 0.3s; + touch-action: manipulation; + min-height: 44px; +} + +@media (min-width: 768px) { + .period-buttons button { + padding: 0.5rem 1rem; + min-height: auto; + } } .period-buttons button:hover { @@ -60,15 +104,30 @@ .date-inputs { display: flex; - align-items: center; + flex-direction: column; gap: 0.5rem; } +@media (min-width: 768px) { + .date-inputs { + flex-direction: row; + align-items: center; + } +} + .date-inputs input[type="date"] { - padding: 0.5rem; + padding: 0.75rem; border: 1px solid #ddd; - border-radius: 4px; + border-radius: 6px; font-size: 0.9rem; + width: 100%; +} + +@media (min-width: 768px) { + .date-inputs input[type="date"] { + padding: 0.5rem; + width: auto; + } } .date-inputs span { @@ -78,27 +137,57 @@ .filter-section input[type="text"], .filter-section select { - padding: 0.5rem; + padding: 0.75rem; border: 1px solid #ddd; - border-radius: 4px; + border-radius: 6px; font-size: 0.9rem; + width: 100%; + -webkit-appearance: none; + appearance: none; +} + +@media (min-width: 768px) { + .filter-section input[type="text"], + .filter-section select { + padding: 0.5rem; + width: auto; + } } .filter-actions { - display: flex; + display: grid; + grid-template-columns: repeat(2, 1fr); gap: 0.5rem; - margin-left: auto; + width: 100%; +} + +@media (min-width: 768px) { + .filter-actions { + display: flex; + margin-left: auto; + width: auto; + } } .btn-primary, .btn-secondary { - padding: 0.5rem 1.5rem; + padding: 0.75rem 1.5rem; border: none; - border-radius: 4px; + border-radius: 6px; cursor: pointer; font-size: 0.9rem; font-weight: 500; transition: all 0.3s; + touch-action: manipulation; + min-height: 44px; +} + +@media (min-width: 768px) { + .btn-primary, + .btn-secondary { + padding: 0.5rem 1.5rem; + min-height: auto; + } } .btn-primary { @@ -128,9 +217,17 @@ .stats-summary { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; - margin-bottom: 2rem; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + margin-bottom: 1rem; +} + +@media (min-width: 768px) { + .stats-summary { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; + margin-bottom: 2rem; + } } .stat-card { @@ -164,6 +261,13 @@ width: 100%; border-collapse: collapse; margin-top: 1rem; + display: none; +} + +@media (min-width: 768px) { + .trades-table { + display: table; + } } .trades-table th { @@ -172,17 +276,104 @@ padding: 1rem; text-align: left; font-weight: 500; + font-size: 0.9rem; } .trades-table td { padding: 0.75rem 1rem; border-bottom: 1px solid #eee; + font-size: 0.9rem; } .trades-table tr:hover { background-color: #f8f9fa; } +/* 移动端卡片式布局 */ +.trades-cards { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 1rem; +} + +@media (min-width: 768px) { + .trades-cards { + display: none; + } +} + +.trade-card { + background: #f8f9fa; + padding: 1rem; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.trade-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; + padding-bottom: 0.75rem; + border-bottom: 1px solid #dee2e6; +} + +.trade-card-symbol { + font-weight: bold; + font-size: 1.1rem; + color: #2c3e50; +} + +.trade-card-side { + padding: 0.25rem 0.75rem; + border-radius: 12px; + font-size: 0.85rem; + font-weight: 500; +} + +.trade-card-side.buy { + background-color: #d4edda; + color: #155724; +} + +.trade-card-side.sell { + background-color: #f8d7da; + color: #721c24; +} + +.trade-card-body { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + margin-bottom: 0.75rem; +} + +.trade-card-field { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.trade-card-label { + font-size: 0.85rem; + color: #666; +} + +.trade-card-value { + font-weight: 500; + color: #2c3e50; +} + +.trade-card-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.75rem; + border-top: 1px solid #dee2e6; + font-size: 0.9rem; +} + .buy { color: #27ae60; font-weight: bold; diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index db05db9..5c5f233 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -204,38 +204,81 @@ const TradeList = () => { {trades.length === 0 ? (
暂无交易记录
) : ( - - - - - - - - - - - - - - - {trades.map(trade => ( - - - - - - - - - + <> + {/* 桌面端表格 */} +
交易对方向数量入场价出场价盈亏状态时间
{trade.symbol}{trade.side}{parseFloat(trade.quantity).toFixed(4)}{parseFloat(trade.entry_price).toFixed(4)}{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}= 0 ? 'positive' : 'negative'}> - {parseFloat(trade.pnl).toFixed(2)} USDT - - {trade.status} - {new Date(trade.entry_time).toLocaleString('zh-CN')}
+ + + + + + + + + + + + + {trades.map(trade => ( + + + + + + + + + + + ))} + +
交易对方向数量入场价出场价盈亏状态时间
{trade.symbol}{trade.side}{parseFloat(trade.quantity).toFixed(4)}{parseFloat(trade.entry_price).toFixed(4)}{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}= 0 ? 'positive' : 'negative'}> + {parseFloat(trade.pnl).toFixed(2)} USDT + + {trade.status} + {new Date(trade.entry_time).toLocaleString('zh-CN')}
+ + {/* 移动端卡片 */} +
+ {trades.map(trade => ( +
+
+ {trade.symbol} + + {trade.side === 'BUY' ? '买入' : '卖出'} · {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} + +
+
+
+ 数量 + {parseFloat(trade.quantity).toFixed(4)} +
+
+ 入场价 + {parseFloat(trade.entry_price).toFixed(4)} +
+
+ 出场价 + {trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'} +
+
+ 盈亏 + = 0 ? 'positive' : 'negative'}`}> + {parseFloat(trade.pnl).toFixed(2)} USDT + +
+
+
+ {new Date(trade.entry_time).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} + {trade.exit_time && ( + 平仓: {new Date(trade.exit_time).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })} + )} +
+
))} - - +
+ )} ) diff --git a/frontend/src/index.css b/frontend/src/index.css index 51923ac..96f8397 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -11,6 +11,20 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #f5f5f5; + /* 移动端优化 */ + -webkit-tap-highlight-color: transparent; + touch-action: manipulation; +} + +/* 移动端全局样式优化 */ +@media (max-width: 767px) { + * { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1); + } + + input, select, textarea, button { + font-size: 16px; /* 防止iOS自动缩放 */ + } } code {