Dashboard: 信号tab + API(signals + xiaoguo-scan),前端展示
This commit is contained in:
@@ -198,7 +198,59 @@ def api_market():
|
||||
return jsonify(_load_json(DATA_DIR / "market.json", {}))
|
||||
|
||||
|
||||
# ── 数据写入API(供 cron/update_data.py 调用) ──────────
|
||||
# ── 信号API(新增) ─────────────────────────────────────
|
||||
|
||||
|
||||
@app.route("/api/signals")
|
||||
def api_signals():
|
||||
"""最近信号 + 小果分析"""
|
||||
try:
|
||||
from mofin_db import get_conn
|
||||
conn = get_conn()
|
||||
signals = conn.execute("""
|
||||
SELECT sn.id, sn.sector, sn.overall_sentiment,
|
||||
sn.summary, sn.source, sn.created_at,
|
||||
ss.signal_type, ss.severity
|
||||
FROM signal_news sn
|
||||
LEFT JOIN sector_signals ss ON sn.signal_id = ss.id
|
||||
ORDER BY sn.id DESC LIMIT 20
|
||||
""").fetchall()
|
||||
conn.close()
|
||||
return jsonify([dict(r) for r in signals])
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/xiaoguo-scan")
|
||||
def api_xiaoguo_scan():
|
||||
"""小果扫描统计"""
|
||||
try:
|
||||
from mofin_db import get_conn
|
||||
conn = get_conn()
|
||||
total = conn.execute("SELECT COUNT(*) FROM xiaoguo_scan_tracker").fetchone()[0]
|
||||
found = conn.execute("SELECT COUNT(*) FROM xiaoguo_scan_tracker WHERE found_count>0").fetchone()[0]
|
||||
recent = conn.execute("""
|
||||
SELECT code, name, last_scanned_at, found_count
|
||||
FROM xiaoguo_scan_tracker
|
||||
ORDER BY last_scanned_at DESC LIMIT 20
|
||||
""").fetchall()
|
||||
source_count = conn.execute("""
|
||||
SELECT source, COUNT(*) as cnt FROM signal_news
|
||||
WHERE datetime(created_at) > datetime('now', '-1 day')
|
||||
GROUP BY source
|
||||
""").fetchall()
|
||||
conn.close()
|
||||
return jsonify({
|
||||
"total_scanned": total,
|
||||
"found_signals": found,
|
||||
"recent": [dict(r) for r in recent],
|
||||
"source_today": {r["source"]: r["cnt"] for r in source_count}
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
# ── 数据写入API ──
|
||||
|
||||
@app.route("/api/update/portfolio", methods=["POST"])
|
||||
def update_portfolio():
|
||||
|
||||
@@ -50,6 +50,7 @@ body { background: #0f0f13; color: #e2e8f0; }
|
||||
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="evaluation">📊 评估</button>
|
||||
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="prompts">📝 提示词</button>
|
||||
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="market">🌐 市场</button>
|
||||
<button class="tab-btn px-4 py-2 text-sm rounded-lg" data-tab="signals">🔍 信号</button>
|
||||
<a href="/upload" class="tab-btn px-4 py-2 text-sm rounded-lg" style="text-decoration:none;color:#fbbf24;border-color:#fbbf24">📸 上传</a>
|
||||
</div>
|
||||
|
||||
@@ -62,6 +63,7 @@ body { background: #0f0f13; color: #e2e8f0; }
|
||||
<div id="tab-evaluation" class="tab-content hidden"></div>
|
||||
<div id="tab-prompts" class="tab-content hidden"></div>
|
||||
<div id="tab-market" class="tab-content hidden"></div>
|
||||
<div id="tab-signals" class="tab-content hidden"></div>
|
||||
</div>
|
||||
|
||||
<!-- Stock Detail Modal -->
|
||||
@@ -144,6 +146,7 @@ function renderTab(name) {
|
||||
else if (name === 'evaluation') renderEvaluation();
|
||||
else if (name === 'prompts') renderPrompts();
|
||||
else if (name === 'market') renderMarket();
|
||||
else if (name === 'signals') renderSignals();
|
||||
}
|
||||
|
||||
// ── Data Fetching ──
|
||||
@@ -1101,6 +1104,67 @@ function closePromptModal() {
|
||||
document.getElementById('promptModal').classList.add('hidden');
|
||||
document.getElementById('promptModal').classList.remove('flex');
|
||||
}
|
||||
|
||||
// ── 信号页 ──
|
||||
async function renderSignals() {
|
||||
const el = document.getElementById('tab-signals');
|
||||
el.innerHTML = '<div class="text-slate-400 text-sm">加载中…</div>';
|
||||
try {
|
||||
const [signals, scan] = await Promise.all([
|
||||
fetchJSON('/api/signals'),
|
||||
fetchJSON('/api/xiaoguo-scan')
|
||||
]);
|
||||
const sourceToday = scan.source_today || {};
|
||||
let html = `
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-slate-500 mb-1">今日信号来源</div>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<span class="text-blue-400">📡 趋势: ${sourceToday.trend || 0}</span>
|
||||
<span class="text-green-400">🔍 小果扫描: ${sourceToday.xiaoguo || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-slate-500 mb-1">小果扫描统计</div>
|
||||
<div class="flex gap-4 text-sm">
|
||||
<span class="text-slate-300">📋 累计扫描: ${scan.total_scanned || 0} 只</span>
|
||||
<span class="text-yellow-400">🏆 发现信号: ${scan.found_signals || 0} 只</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (!signals || signals.length === 0) {
|
||||
html += '<div class="card p-6 text-center text-slate-500 text-sm">暂无信号</div>';
|
||||
el.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
|
||||
html += '<div class="space-y-3">';
|
||||
for (const s of signals) {
|
||||
const sev = s.severity || '-';
|
||||
const sevColor = sev === 'high' ? 'text-red-400' : sev === 'medium' ? 'text-yellow-400' : 'text-green-400';
|
||||
const sent = s.overall_sentiment || '-';
|
||||
const sentColor = sent === '利好' ? 'text-green-400' : sent === '利空' ? 'text-red-400' : 'text-slate-400';
|
||||
const source = s.source || 'trend';
|
||||
const sourceIcon = source === 'xiaoguo' ? '🔍' : '📡';
|
||||
html += `
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="${sevColor} text-xs font-semibold">${sev.toUpperCase()}</span>
|
||||
<span class="${sentColor} text-sm font-semibold">${sent}</span>
|
||||
<span class="text-slate-500 text-xs">${sourceIcon} ${source}</span>
|
||||
<span class="text-slate-600 text-xs ml-auto">${s.created_at ? s.created_at.slice(5,16) : ''}</span>
|
||||
</div>
|
||||
<div class="text-sm text-slate-300">${s.sector || '-'}</div>
|
||||
<div class="text-xs text-slate-500 mt-1">${(s.summary || '').slice(0,120)}</div>
|
||||
</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
el.innerHTML = html;
|
||||
} catch(e) {
|
||||
el.innerHTML = `<div class="text-red-400 text-sm">加载失败: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user