This commit is contained in:
薇薇安 2026-01-15 16:58:05 +08:00
parent db2bd6a3b2
commit 2e672d1f25
3 changed files with 83 additions and 7 deletions

View File

@ -118,10 +118,22 @@ async def get_active_recommendations():
"""
获取当前有效的推荐(未过期、未执行、未取消)
使用WebSocket实时价格更新推荐中的价格信息
同一交易对只返回最新的推荐(已去重)
"""
try:
recommendations = TradeRecommendation.get_active()
# 确保时间格式正确(转换为ISO格式字符串,包含时区信息)
from datetime import datetime
for rec in recommendations:
if rec.get('recommendation_time'):
if isinstance(rec['recommendation_time'], datetime):
# 如果是datetime对象,转换为ISO格式字符串(UTC+8)
rec['recommendation_time'] = rec['recommendation_time'].isoformat()
elif isinstance(rec['recommendation_time'], str):
# 如果已经是字符串,确保格式正确
pass
# 尝试从WebSocket缓存获取实时价格更新推荐中的价格
try:
import sys

View File

@ -322,11 +322,19 @@ class TradeRecommendation:
@staticmethod
def get_active():
"""获取当前有效的推荐(未过期、未执行、未取消)"""
"""获取当前有效的推荐(未过期、未执行、未取消)
同一交易对只返回最新的推荐(去重)
"""
return db.execute_query(
"""SELECT * FROM trade_recommendations
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY signal_strength DESC, recommendation_time DESC"""
"""SELECT t1.* FROM trade_recommendations t1
INNER JOIN (
SELECT symbol, MAX(recommendation_time) as max_time
FROM trade_recommendations
WHERE status = 'active' AND (expires_at IS NULL OR expires_at > NOW())
GROUP BY symbol
) t2 ON t1.symbol = t2.symbol AND t1.recommendation_time = t2.max_time
WHERE t1.status = 'active' AND (t1.expires_at IS NULL OR t1.expires_at > NOW())
ORDER BY t1.signal_strength DESC, t1.recommendation_time DESC"""
)
@staticmethod

View File

@ -14,10 +14,56 @@ function Recommendations() {
useEffect(() => {
loadRecommendations()
// 如果是查看有效推荐,每10秒自动刷新一次(获取实时价格)
// 如果是查看有效推荐,每10秒静默更新价格(不触发loading状态)
let interval = null
if (statusFilter === 'active') {
interval = setInterval(loadRecommendations, 10000) // 每10秒刷新
interval = setInterval(async () => {
// 静默更新:只更新价格,不显示loading
try {
const result = await api.getActiveRecommendations()
const newData = result.data || []
// 使用setState直接更新,不触发loading状态
setRecommendations(prevRecommendations => {
// 如果新数据为空,保持原数据不变
if (newData.length === 0) {
return prevRecommendations
}
// 创建一个映射,用于快速查找
const newDataMap = new Map(newData.map(rec => [rec.id, rec]))
const prevMap = new Map(prevRecommendations.map(rec => [rec.id, rec]))
// 合并数据:优先使用新数据(包含实时价格更新)
const updated = prevRecommendations.map(prevRec => {
const newRec = newDataMap.get(prevRec.id)
if (newRec) {
// 如果新数据中有该推荐,使用新数据(包含价格更新)
return newRec
}
// 如果新数据中没有,保留旧数据(可能已过期或状态改变)
return prevRec
})
// 添加新出现的推荐
const newItems = newData.filter(newRec => !prevMap.has(newRec.id))
// 合并并去重(按id)
const merged = [...updated, ...newItems]
const uniqueMap = new Map()
merged.forEach(rec => {
if (!uniqueMap.has(rec.id)) {
uniqueMap.set(rec.id, rec)
}
})
return Array.from(uniqueMap.values())
})
} catch (err) {
// 静默失败,不显示错误
console.debug('静默更新价格失败:', err)
}
}, 10000) // 每10秒刷新
}
return () => {
@ -104,14 +150,24 @@ function Recommendations() {
const formatTime = (timeStr) => {
if (!timeStr) return '-'
try {
// 处理时区问题:如果时间字符串包含时区信息,直接解析
// 否则假设是UTC时间,转换为本地时间
const date = new Date(timeStr)
// 检查日期是否有效
if (isNaN(date.getTime())) {
return timeStr
}
// 使用本地时区格式化(显示北京时间,如果服务器返回的是UTC时间)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
second: '2-digit',
timeZone: 'Asia/Shanghai' // 明确使用北京时间
})
} catch (e) {
return timeStr