diff --git a/frontend/src/components/Recommendations.css b/frontend/src/components/Recommendations.css index 357bf69..8eea58d 100644 --- a/frontend/src/components/Recommendations.css +++ b/frontend/src/components/Recommendations.css @@ -352,6 +352,27 @@ justify-content: flex-end; } +.ug-direction-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 999px; + border: 2px solid rgba(255, 255, 255, 0.9); + background: rgba(0, 0, 0, 0.15); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.22); + backdrop-filter: blur(4px); +} + +.ug-direction-badge.buy { + background: rgba(46, 204, 113, 0.22); +} + +.ug-direction-badge.sell { + background: rgba(231, 76, 60, 0.22); +} + .ug-pill { display: inline-block; padding: 4px 8px; @@ -364,18 +385,29 @@ .ug-direction-icon { flex: 0 0 auto; - margin-right: 2px; opacity: 0.95; } -.ug-direction-icon.buy { +.ug-direction-icon { color: rgba(255, 255, 255, 0.95); filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.15)); } -.ug-direction-icon.sell { - color: rgba(255, 255, 255, 0.95); - filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.15)); +.ug-entry-status { + margin-left: 6px; + font-size: 11px; + padding: 2px 6px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + border: 1px solid rgba(255, 255, 255, 0.18); +} + +.ug-entry-status.hit { + background: rgba(255, 255, 255, 0.18); +} + +.ug-entry-status.wait { + opacity: 0.9; } .ug-pill.entry { diff --git a/frontend/src/components/Recommendations.jsx b/frontend/src/components/Recommendations.jsx index 57b53f5..a469d38 100644 --- a/frontend/src/components/Recommendations.jsx +++ b/frontend/src/components/Recommendations.jsx @@ -185,12 +185,34 @@ function Recommendations() { const formatTime = (timeStr) => { if (!timeStr) return '-' try { - // 处理时区问题:如果时间字符串包含时区信息,直接解析 - // 否则假设是UTC时间,转换为本地时间 - const date = new Date(timeStr) + // 统一按“北京时间”显示,但要先把输入解析成正确的时间点(epoch) + // 注意:部分来源会给出不带时区的字符串(如 "2026-01-19T05:54:04" 或 "2026-01-19 05:54:04") + // 这类字符串在浏览器里会被当作“本地时间”解析,导致显示偏差。 + const raw = String(timeStr).trim() + + let date = null + + // 1) 纯数字:兼容 seconds/ms + if (/^\d+$/.test(raw)) { + const n = Number(raw) + const ms = raw.length >= 13 ? n : n * 1000 + date = new Date(ms) + } else { + // 2) "YYYY-MM-DD HH:mm:ss" -> 当作 UTC(历史推荐里常见),再转北京时间显示 + if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(raw)) { + const isoUtc = raw.replace(' ', 'T') + 'Z' + date = new Date(isoUtc) + } else if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(raw)) { + // 3) ISO 但无时区 -> 当作 UTC + date = new Date(raw + 'Z') + } else { + // 4) 其他格式(包含 Z / +08:00 / RFC1123 等)交给浏览器解析 + date = new Date(raw) + } + } // 检查日期是否有效 - if (isNaN(date.getTime())) { + if (!date || isNaN(date.getTime())) { return timeStr } @@ -209,6 +231,20 @@ function Recommendations() { } } + const getEntryStatus = (rec) => { + try { + const entry = Number(rec?.suggested_limit_price ?? rec?.planned_entry_price) + const cur = Number(rec?.current_price) + if (!isFinite(entry) || !isFinite(cur) || !rec?.direction) return null + if (rec.direction === 'BUY') { + return cur <= entry ? { text: '已到价', cls: 'hit' } : { text: '未到价', cls: 'wait' } + } + return cur >= entry ? { text: '已到价', cls: 'hit' } : { text: '未到价', cls: 'wait' } + } catch (e) { + return null + } + } + const formatTimeMs = (ms) => { if (!ms && ms !== 0) return '-' try { @@ -346,7 +382,9 @@ function Recommendations() { 信号强度: {rec.signal_strength}/10 - {formatTime(rec.recommendation_time)} + + {formatTime(rec.recommendation_time)}(北京) + @@ -407,13 +445,19 @@ function Recommendations() {
📋 操作计划
- {rec.direction === 'BUY' ? ( - - ) : ( - - )} + + {rec.direction === 'BUY' ? ( + + ) : ( + + )} + 入场 {fmtPrice(rec.suggested_limit_price ?? rec.planned_entry_price)} USDT + {(() => { + const st = getEntryStatus(rec) + return st ? {st.text} : null + })()} 止损 {fmtPrice(rec.suggested_stop_loss)} USDT @@ -430,62 +474,6 @@ function Recommendations() {
)} - {/* 交易教程(如果存在) */} - {rec.trading_tutorial && ( -
- 💡 交易提示: -

{rec.trading_tutorial}

-
- )} - -
- 技术分析原因: -

{rec.recommendation_reason || '-'}

-
- -
-
- - - 限价单 - -
- {rec.suggested_limit_price && ( -
- - - {fmtPrice(rec.suggested_limit_price)} USDT - {rec.current_price && ( - - ({rec.direction === 'BUY' ? '低于' : '高于'}当前价 - {Math.abs(((rec.suggested_limit_price - rec.current_price) / rec.current_price) * 100).toFixed(2)}%) - - )} - -
- )} -
- - {fmtPrice(rec.suggested_stop_loss)} -
-
- - {fmtPrice(rec.suggested_take_profit_1)} -
-
- - {fmtPrice(rec.suggested_take_profit_2)} -
-
- - {(parseFloat(rec.suggested_position_percent || 0) * 100).toFixed(2)}% -
-
- - {rec.suggested_leverage || 10}x -
-
-
{typeFilter === 'realtime' && (