166 lines
15 KiB
HTML
166 lines
15 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}AlphaX Agent | Crypto — 市场总览{% endblock %}
|
||
{% block nav_links %}
|
||
<a class="sidebar-link" href="/app"><svg class="link-icon"><use href="#svg-dashboard"/></svg>看板</a>
|
||
<a class="sidebar-link active" href="/market"><svg class="link-icon"><use href="#svg-target"/></svg>市场总览</a>
|
||
<a class="sidebar-link" href="/sentiment"><svg class="link-icon"><use href="#svg-sentiment"/></svg>舆情</a>
|
||
<a class="sidebar-link" href="/onchain"><svg class="link-icon"><use href="#svg-onchain"/></svg>链上异动</a>
|
||
<a class="sidebar-link" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
||
<a class="sidebar-link" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>推荐</a>
|
||
<div class="sidebar-section-label admin-link" style="display:none">研发</div>
|
||
<a class="sidebar-link admin-link" href="/pipeline" style="display:none"><svg class="link-icon"><use href="#svg-pipeline"/></svg>链路日志</a>
|
||
<a class="sidebar-link admin-link" href="/cron" style="display:none"><svg class="link-icon"><use href="#svg-cron"/></svg>调度中心</a>
|
||
<a class="sidebar-link admin-link" href="/llm-insights" style="display:none"><svg class="link-icon"><use href="#svg-ai"/></svg>AI 记录</a>
|
||
<a class="sidebar-link admin-link" href="/strategy" style="display:none"><svg class="link-icon"><use href="#svg-target"/></svg>策略</a>
|
||
<a class="sidebar-link admin-link" href="/iteration" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>迭代</a>
|
||
<a class="sidebar-link admin-link" href="/admin.html" style="display:none"><svg class="link-icon"><use href="#svg-admin"/></svg>管理</a>
|
||
{% endblock %}
|
||
{% block extra_head_css %}
|
||
<style>
|
||
.shell{width:min(100% - 40px,1280px);margin:0 auto;padding:24px 0 44px}.page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin-bottom:16px;flex-wrap:wrap}.page-head h1{font-size:28px;font-weight:950;letter-spacing:-.8px;color:var(--ink)}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.55;max-width:880px}.head-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.select,.btn{height:38px;border:1px solid var(--hairline-strong);background:var(--canvas);border-radius:var(--radius-md);padding:0 12px;font-size:13px;font-weight:850;color:var(--ink)}.btn{cursor:pointer}.hint{padding:10px 12px;border:1px solid rgba(66,98,255,.14);background:rgba(66,98,255,.045);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.55;margin-bottom:14px}.kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:14px}.kpi{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);padding:13px;min-width:0}.kpi span{display:block;color:var(--stone);font-size:11px;font-weight:900}.kpi b{display:block;margin-top:7px;color:var(--ink);font-size:22px;line-height:1;font-weight:950;letter-spacing:-.5px}.kpi b.green{color:var(--green)}.kpi b.red{color:var(--red)}.kpi b.blue{color:var(--blue)}.kpi b.yellow{color:var(--yellow-dark)}.grid{display:grid;grid-template-columns:1.1fr .9fr;gap:12px;margin-bottom:14px}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);overflow:hidden;min-width:0}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:13px 14px;border-bottom:1px solid var(--hairline-soft)}.panel-title{font-size:14px;font-weight:950;color:var(--ink)}.panel-note{font-size:11px;color:var(--stone);font-weight:850}.panel-body{padding:12px}.mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.mini{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px;min-width:0}.mini span{display:block;color:var(--stone);font-size:10px;font-weight:900}.mini b{display:block;margin-top:4px;color:var(--ink);font-size:15px;font-weight:950;line-height:1.3}.line{display:flex;justify-content:space-between;gap:10px;align-items:center;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px 11px;margin-bottom:8px}.line .lbl{font-size:12px;font-weight:900;color:var(--ink)}.line .val{font-size:12px;color:var(--slate);font-weight:850;text-align:right}.chips{display:flex;flex-wrap:wrap;gap:8px}.chip{display:inline-flex;align-items:center;gap:6px;padding:7px 10px;border-radius:999px;background:var(--surface);border:1px solid var(--hairline-soft);font-size:12px;font-weight:850;color:var(--slate)}.chip.hot{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.chip.risk{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}.chip.blue{background:rgba(66,98,255,.06);border-color:rgba(66,98,255,.16);color:var(--blue)}.raw-list{display:grid;gap:8px}.raw-item{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px}.raw-item h3{font-size:12px;font-weight:950;color:var(--ink);line-height:1.45}.raw-item .sub{margin-top:6px;color:var(--stone);font-size:11px;line-height:1.45}.raw-tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.tag{display:inline-flex;padding:3px 7px;border-radius:999px;background:var(--canvas);border:1px solid var(--hairline-soft);font-size:10px;font-weight:900;color:var(--blue)}.tag.risk{color:var(--red)}.tag.hot{color:var(--green)}.empty,.loading{padding:34px 16px;text-align:center;color:var(--stone);font-size:13px}.soft-note{padding:10px 12px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.5}.compact-note{color:var(--stone);font-size:12px;line-height:1.5}.status-pill{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px;border-radius:999px;border:1px solid var(--hairline-soft);background:var(--surface);font-size:12px;font-weight:900;color:var(--slate)}.status-pill.ok{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.status-pill.warn{background:var(--yellow-light);border-color:rgba(252,185,0,.22);color:var(--yellow-dark)}.status-pill.bad{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}@media(max-width:1080px){.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.grid{grid-template-columns:1fr}}@media(max-width:620px){.shell{width:min(100% - 24px,1280px)}.page-head h1{font-size:22px}.mini-grid{grid-template-columns:1fr}}
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
<div class="shell">
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>市场总览</h1>
|
||
<p>这里不是单币推荐页,而是帮你快速判断当前环境:场内有没有动能,链上有没有真异动,AI 舆情有没有明确倾向。</p>
|
||
</div>
|
||
<div class="head-actions">
|
||
<select class="select" id="hoursSel" onchange="reloadAll()">
|
||
<option value="24">近 24h</option>
|
||
<option value="72">近 3 天</option>
|
||
<option value="168">近 7 天</option>
|
||
</select>
|
||
<button class="btn" onclick="reloadAll()">刷新</button>
|
||
</div>
|
||
</div>
|
||
<div class="hint">只保留高信号信息。空的 AI / 舆情 / 链上统计不占主位,避免页面看起来热闹但没有结论。</div>
|
||
<div class="kpis" id="kpis"><div class="loading">加载中...</div></div>
|
||
<div class="grid">
|
||
<section class="panel">
|
||
<div class="panel-head"><div class="panel-title">市场结论</div><div class="panel-note">场内 + 链上 + AI 的最短摘要</div></div>
|
||
<div class="panel-body" id="summaryPanel"><div class="loading">加载中...</div></div>
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel-head"><div class="panel-title">重点板块</div><div class="panel-note">只看有热度的板块</div></div>
|
||
<div class="panel-body" id="sectorPanel"><div class="loading">加载中...</div></div>
|
||
</section>
|
||
</div>
|
||
<div class="grid">
|
||
<section class="panel">
|
||
<div class="panel-head"><div class="panel-title">链上态势</div><div class="panel-note">原始覆盖与高价值异动</div></div>
|
||
<div class="panel-body" id="onchainPanel"><div class="loading">加载中...</div></div>
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel-head"><div class="panel-title">原始链上流</div><div class="panel-note">实时事件先看事实</div></div>
|
||
<div class="panel-body" id="rawPanel"><div class="loading">加载中...</div></div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
{% block extra_script %}
|
||
<script>
|
||
var API = '';
|
||
function $(id){return document.getElementById(id)}
|
||
function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]})}
|
||
function fmtUsd(v){v=Number(v||0);if(Math.abs(v)>=1e9)return '$'+(v/1e9).toFixed(2)+'B';if(Math.abs(v)>=1e6)return '$'+(v/1e6).toFixed(2)+'M';if(Math.abs(v)>=1e3)return '$'+(v/1e3).toFixed(1)+'K';return '$'+v.toFixed(0)}
|
||
function fmtNum(v,d){v=Number(v||0);return v.toFixed(d==null?1:d)}
|
||
function chip(text, cls){return '<span class="chip '+(cls||'')+'">'+esc(text)+'</span>'}
|
||
function compact(text){return esc(String(text||'').replace(/\s+/g,' ').trim())}
|
||
function loadKpis(stats, onchain, news, ai){
|
||
var fg=(news&&news.fear_greed)||{};
|
||
var market=(stats&&stats.market_context_overview)||{};
|
||
var onchainKpi=(onchain&&onchain.kpi)||{};
|
||
var aiStatus=(ai&&ai.status)||'--';
|
||
$('kpis').innerHTML=[
|
||
['情绪指数', fg.value!=null?fg.value:'--', 'blue'],
|
||
['场内热点', market.hot_sector_count||0, 'green'],
|
||
['链上原始流', onchainKpi.raw_event_count||0, 'blue'],
|
||
['AI 状态', aiStatus, 'yellow']
|
||
].map(function(x){return '<div class="kpi"><span>'+x[0]+'</span><b class="'+x[2]+'">'+x[1]+'</b></div>'}).join('');
|
||
}
|
||
function renderSummary(stats, onchain, news, ai){
|
||
var market=(stats&&stats.market_context_overview)||{};
|
||
var fg=(news&&news.fear_greed)||{};
|
||
var aiContent=(ai&&ai.content)||{};
|
||
var summary=(aiContent.summary||aiContent.memo||aiContent.why_now_or_not||'').trim();
|
||
var mood = '观望';
|
||
var moodClass = 'warn';
|
||
var heat = Number(market.avg_turnover_acceleration_1h||0);
|
||
if (heat >= 1.2) { mood='偏机会'; moodClass='ok'; }
|
||
else if (heat <= 0.7) { mood='偏谨慎'; moodClass='bad'; }
|
||
var aiStatus = (ai&&ai.status) || '--';
|
||
var aiText = summary || '暂无 AI 舆情摘要';
|
||
$('summaryPanel').innerHTML =
|
||
'<div class="line"><span class="lbl">市场温度</span><span class="val"><span class="status-pill '+moodClass+'">'+mood+'</span></span></div>'+
|
||
'<div class="line"><span class="lbl">动能</span><span class="val">'+fmtNum(market.avg_turnover_acceleration_1h||0,1)+'x / '+fmtNum(market.avg_turnover_acceleration_4h||0,1)+'x</span></div>'+
|
||
'<div class="line"><span class="lbl">成交额</span><span class="val">'+fmtUsd(market.avg_volume_24h||0)+'</span></div>'+
|
||
'<div class="line"><span class="lbl">情绪指数</span><span class="val">'+esc((fg.classification||'--')+' · '+(fg.value!=null?fg.value:'--'))+'</span></div>'+
|
||
'<div class="line"><span class="lbl">AI 舆情</span><span class="val">'+esc(aiStatus)+'</span></div>'+
|
||
'<div class="soft-note">'+compact(aiText)+'</div>';
|
||
}
|
||
function renderSectors(stats){
|
||
var sectors=((stats&&stats.market_context_overview||{}).top_hot_sectors)||[];
|
||
if (!sectors.length) { $('sectorPanel').innerHTML = '<div class="empty">暂无热点板块</div>'; return; }
|
||
$('sectorPanel').innerHTML = '<div class="chips">'+sectors.map(function(sec){return chip((sec.sector||'--')+' · '+(sec.count||0),'hot')}).join('')+'</div>';
|
||
}
|
||
function renderOnchain(d){
|
||
var k=(d&&d.kpi)||{};
|
||
var hot=(d.hot_tokens||[]).slice(0,4);
|
||
var risk=(d.risk_tokens||[]).slice(0,4);
|
||
var lines = [];
|
||
lines.push(['原始事件', k.raw_event_count||0]);
|
||
lines.push(['已映射', k.raw_mapped_count||0]);
|
||
lines.push(['覆盖币种', k.token_count||0]);
|
||
lines.push(['正向异动', k.positive_events||0]);
|
||
lines.push(['风险事件', k.risk_events||0]);
|
||
lines.push(['DEX 成交', fmtUsd(k.dex_volume_usd||0)]);
|
||
var body = '<div class="mini-grid">'+lines.map(function(x){return '<div class="mini"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')+'</div>';
|
||
var hotHtml = hot.length ? '<div style="margin-top:10px"><div class="panel-note" style="margin-bottom:8px">链上热度</div><div class="chips">'+hot.map(function(t){return chip((t.symbol||'--')+' · '+fmtUsd(t.dex_volume_usd),'hot')}).join('')+'</div></div>' : '';
|
||
var riskHtml = risk.length ? '<div style="margin-top:10px"><div class="panel-note" style="margin-bottom:8px">风险异动</div><div class="chips">'+risk.map(function(t){return chip((t.symbol||'--')+' · '+Number(t.risk_score||0).toFixed(0),'risk')}).join('')+'</div></div>' : '';
|
||
$('onchainPanel').innerHTML = body + hotHtml + riskHtml;
|
||
}
|
||
function renderRaw(d){
|
||
var items=(d&&d.raw_events)||[];
|
||
if (!items.length) { $('rawPanel').innerHTML = '<div class="empty">暂无原始链上流</div>'; return; }
|
||
$('rawPanel').innerHTML = '<div class="raw-list">'+items.slice(0,6).map(function(e){
|
||
var mapped = e.mapping_status === 'mapped';
|
||
return '<div class="raw-item"><h3>'+compact(e.title||e.event_label||e.event_type||'链上原始事件')+' <span class="status-pill '+(mapped?'ok':'warn')+'" style="margin-left:6px;height:22px">'+(mapped?'已映射':'未映射')+'</span></h3><div class="sub">'+esc((e.chain||'--')+' · '+(e.mapped_symbol||'未映射')+' · '+fmtUsd(e.total_amount||e.amount||0)+' · '+(e.detected_at||'--'))+'</div><div class="raw-tags">'+[(e.source||''),(e.event_type||''),(e.url?'来源':'')].filter(Boolean).map(function(t){return '<span class="tag">'+esc(t)+'</span>'}).join('')+'</div></div>';
|
||
}).join('')+'</div>';
|
||
}
|
||
function renderAiStatus(ai){
|
||
var content=(ai&&ai.content)||{};
|
||
var summary=(content.summary||content.memo||content.why_now_or_not||'').trim();
|
||
if (!summary) return '';
|
||
return '<div class="soft-note" style="margin-top:10px">AI 结论:'+compact(summary)+'</div>';
|
||
}
|
||
async function reloadAll(){
|
||
try{
|
||
var hours=$('hoursSel').value;
|
||
var resp=await fetch(API+'/api/market/overview?hours='+hours);
|
||
var d=await resp.json();
|
||
var market=(d.market||{}).stats||{};
|
||
var onchain=(d.market||{}).onchain||{};
|
||
var news=(d.market||{}).newsfeed||{};
|
||
var ai=(d.market||{}).ai_analysis||{};
|
||
loadKpis(market,onchain,news,ai);
|
||
renderSummary(market,onchain,news,ai);
|
||
renderSectors(market);
|
||
renderOnchain(onchain);
|
||
renderRaw(onchain);
|
||
var aiTail = renderAiStatus(ai);
|
||
if (aiTail) $('summaryPanel').innerHTML += aiTail;
|
||
}catch(e){
|
||
$('kpis').innerHTML='<div class="empty">市场总览加载失败</div>';
|
||
$('summaryPanel').innerHTML='';
|
||
$('sectorPanel').innerHTML='';
|
||
$('onchainPanel').innerHTML='';
|
||
$('rawPanel').innerHTML='';
|
||
}
|
||
}
|
||
reloadAll();
|
||
</script>
|
||
{% endblock %}
|