alphax/static/onchain.html
2026-05-21 20:45:43 +08:00

84 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}AlphaX Agent — 链上异动{% 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:26px;font-weight:900;letter-spacing:-.5px;color:var(--ink)}.page-head p{margin-top:5px;color:var(--stone);font-size:13px;line-height:1.45}.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}.kpis{display:grid;grid-template-columns:repeat(6,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:23px;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)}.source-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:14px}.source-card{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);padding:12px;min-width:0}.source-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.source-name{font-size:13px;font-weight:950;color:var(--ink)}.source-role{color:var(--stone);font-size:11px;line-height:1.45;min-height:32px}.source-stats{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}.source-foot{margin-top:9px;color:var(--stone);font-size:11px;line-height:1.45}.flow-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px}.flow-step{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px}.flow-step span{display:block;color:var(--stone);font-size:10px;font-weight:900}.flow-step b{display:block;margin-top:5px;color:var(--ink);font-size:20px;font-weight:950}.spotlight{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);min-width:0;overflow:hidden}.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}.raw-panel{margin-bottom:14px}.raw-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;border-bottom:1px solid var(--hairline-soft);background:var(--surface);flex-wrap:wrap}.raw-feed{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;padding:12px}.raw-card{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px;min-width:0}.raw-card.low{opacity:.72}.raw-card.medium{border-color:rgba(66,98,255,.16)}.raw-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.raw-title{font-size:12px;font-weight:950;color:var(--ink);line-height:1.35;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.raw-desc{color:var(--slate);font-size:12px;line-height:1.45;min-height:52px}.raw-meaning{margin-top:7px;color:var(--stone);font-size:11px;line-height:1.45}.raw-meta{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:9px}.raw-link{color:var(--blue);font-size:11px;font-weight:900;text-decoration:none}.token-list{display:grid;gap:8px;padding:12px}.token-row{display:grid;grid-template-columns:1fr auto;gap:8px;border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px;cursor:pointer;transition:.12s}.token-row:hover{border-color:var(--hairline);background:rgba(66,98,255,.04)}.token-main{min-width:0}.sym{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:950;color:var(--ink);font-size:14px}.sub{margin-top:4px;color:var(--stone);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.score{display:grid;place-items:center;min-width:58px;height:42px;border-radius:var(--radius-md);border:1px solid var(--hairline-soft);background:var(--canvas);font-weight:950;color:var(--blue)}.score.risk{color:var(--red)}.layout{display:grid;grid-template-columns:minmax(0,1fr) 380px;gap:14px;align-items:start}.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:12px 14px;border-bottom:1px solid var(--hairline-soft);background:var(--surface)}.table-wrap{overflow:auto}.table{width:100%;border-collapse:collapse;min-width:860px}.table th,.table td{padding:10px 11px;border-bottom:1px solid var(--hairline-soft);text-align:left;font-size:12px;vertical-align:middle}.table th{background:var(--surface);font-size:11px;color:var(--stone);font-weight:950;position:sticky;top:0}.table td{color:var(--slate)}.table tr{cursor:pointer}.table tr:hover td{background:rgba(66,98,255,.035)}.badge{display:inline-flex;align-items:center;height:24px;border-radius:var(--radius-full);padding:0 8px;border:1px solid var(--hairline-soft);background:var(--surface);font-size:11px;font-weight:900;color:var(--slate);white-space:nowrap}.badge.pos{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.badge.risk{background:var(--red-light);border-color:rgba(229,62,62,.18);color:var(--red)}.badge.blue{background:rgba(66,98,255,.07);border-color:rgba(66,98,255,.16);color:var(--blue)}.badge.mapped{background:var(--green-light);border-color:rgba(0,180,115,.18);color:var(--green)}.badge.unmapped{background:rgba(66,98,255,.07);border-color:rgba(66,98,255,.16);color:var(--blue)}.badge.warn{background:rgba(255,184,0,.12);border-color:rgba(255,184,0,.22);color:#8a5a00}.num{font-weight:950;color:var(--ink);font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.pos-text{color:var(--green)}.neg-text{color:var(--red)}.detail-body{padding:14px}.empty,.loading{padding:34px 16px;text-align:center;color:var(--stone);font-size:13px}.detail-title{font-size:18px;font-weight:950;color:var(--ink);margin-bottom:5px}.detail-sub{color:var(--stone);font-size:12px;margin-bottom:12px}.metric-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.metric{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:10px}.metric span{display:block;font-size:10px;color:var(--stone);font-weight:900}.metric b{display:block;margin-top:4px;color:var(--ink);font-size:15px;font-weight:950}.event-feed{display:grid;gap:8px}.event{border:1px solid var(--hairline-soft);border-radius:var(--radius-md);background:var(--surface);padding:10px}.event-top{display:flex;justify-content:space-between;gap:8px;align-items:center}.event-title{font-size:12px;font-weight:950;color:var(--ink);line-height:1.45}.event-meta{margin-top:5px;color:var(--stone);font-size:11px;line-height:1.45}.pager{display:flex;align-items:center;justify-content:center;gap:10px;padding:12px;border-top:1px solid var(--hairline-soft);color:var(--stone);font-size:12px;font-weight:850}.pager button{height:32px;border:1px solid var(--hairline-strong);background:var(--canvas);border-radius:var(--radius-md);padding:0 10px;font-weight:850;color:var(--ink);cursor:pointer}.pager button:disabled{opacity:.45;cursor:default}.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}@media(max-width:1080px){.kpis{grid-template-columns:repeat(3,minmax(0,1fr))}.source-strip{grid-template-columns:1fr}.flow-strip{grid-template-columns:repeat(2,minmax(0,1fr))}.raw-feed{grid-template-columns:1fr 1fr}.spotlight,.layout{grid-template-columns:1fr}}@media(max-width:620px){.shell{width:min(100% - 24px,1280px)}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.flow-strip{grid-template-columns:1fr}.raw-feed{grid-template-columns:1fr}.page-head h1{font-size:22px}.metric-grid{grid-template-columns:1fr}}
</style>
{% endblock %}
{% block content %}
<div class="shell">
<div class="page-head">
<div><h1>链上异动</h1><p>以 NodeReal 为主数据源,跟踪 EVM 大额转账、holder 变化和鲸鱼行为。链上信号只负责发现线索,最终仍交给技术确认。</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">链上异动不是买入指令。高质量正向信号会进入技术检查;交易所流入、流动性撤出、持仓集中等负向信号只作为风险上下文。</div>
<div class="source-strip" id="providerStatus"><div class="loading">加载数据源状态...</div></div>
<div class="flow-strip" id="flowStatus"></div>
<section class="panel raw-panel">
<div class="panel-head"><div class="panel-title">重要链上事件</div><div class="panel-note">NodeReal 主链路事件优先</div></div>
<div class="raw-toolbar">
<div class="head-actions">
<button class="btn" onclick="setRawPriority('important')">重要事件</button>
<button class="btn" onclick="setRawPriority('low')">低优先级</button>
<button class="btn" onclick="setRawPriority('')">全部原始流</button>
<select class="select" id="rawChainSel" onchange="reloadRawEvents(0)"><option value="">全部链</option><option value="ethereum">Ethereum</option><option value="bsc">BSC</option></select>
<select class="select" id="rawMapSel" onchange="reloadRawEvents(0)"><option value="">全部状态</option><option value="mapped">已映射</option><option value="unmapped">未映射</option></select>
</div>
<div class="panel-note" id="rawInfo">--</div>
</div>
<div class="raw-feed" id="rawFeed"><div class="loading">加载中...</div></div>
</section>
<div class="kpis" id="kpis"><div class="loading">加载中...</div></div>
<div class="spotlight">
<section class="panel"><div class="panel-head"><div class="panel-title">映射信号流</div><div class="panel-note">mapped_signal_feed可进入分析</div></div><div class="token-list" id="hotTokens"><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="token-list" id="riskTokens"><div class="loading">加载中...</div></div></section>
</div>
<div class="layout">
<section class="panel">
<div class="toolbar">
<select class="select" id="chainSel" onchange="reloadTokens(0)"><option value="">全部链</option><option value="ethereum">Ethereum</option><option value="bsc">BSC</option></select>
<select class="select" id="signalSel" onchange="reloadTokens(0)"><option value="">全部信号</option><option value="large_token_transfer">大额转账</option><option value="whale_accumulation">鲸鱼增持</option><option value="holder_growth">持有人增长</option><option value="exchange_outflow">交易所流出</option><option value="exchange_inflow_risk">交易所流入</option><option value="holder_concentration_risk">持仓集中风险</option></select>
</div>
<div class="table-wrap"><table class="table"><thead><tr><th>币种</th><th></th><th>链上分</th><th>风险分</th><th>映射事件</th><th>最近事件</th><th>数据源</th><th>主链路</th></tr></thead><tbody id="tokenTable"><tr><td colspan="8" class="loading">加载中...</td></tr></tbody></table></div>
<div class="pager"><button id="prevBtn" onclick="page(-1)">上一页</button><span id="pageInfo">--</span><button id="nextBtn" onclick="page(1)">下一页</button></div>
</section>
<section class="panel">
<div class="panel-head"><div class="panel-title">单币详情</div><div class="panel-note" id="detailNote">选择币种</div></div>
<div class="detail-body" id="detailBody"><div class="empty">从列表中选择一个币,查看链上事件与主链路关系。</div></div>
</section>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
var API='', state={offset:0,limit:24,total:0,selected:'',rawOffset:0,rawLimit:12,rawTotal:0,rawPriority:'important'};
function $(id){return document.getElementById(id)}
function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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 fmtPct(v){v=Number(v||0);var cls=v>0?'pos-text':v<0?'neg-text':'';return '<span class="'+cls+'">'+(v>0?'+':'')+v.toFixed(1)+'%</span>'}
function fmtTime(t){if(!t)return'--';var d=new Date(t);if(isNaN(d.getTime()))return t;return (d.getMonth()+1)+'/'+d.getDate()+' '+String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0')}
function fmtAmount(v){v=Number(v||0);if(v>=1000)return v.toFixed(0);if(v>0)return v.toFixed(2);return '--'}
function recLabel(r){if(!r||!r.has_active)return '<span class="badge">未进入</span>';var s=r.execution_status||'';if(s==='buy_now')return '<span class="badge pos">入场窗口</span>';if(s==='wait_pullback')return '<span class="badge blue">等回踩</span>';return '<span class="badge blue">'+esc(r.action_status||'观察中')+'</span>'}
function setRawPriority(v){state.rawPriority=v;reloadRawEvents(0)}
function statusBadgeClass(s){s=String(s||'');if(s.indexOf('正常')>=0)return 'pos';if(s.indexOf('失败')>=0||s.indexOf('未接入')>=0)return 'warn';if(s.indexOf('关闭')>=0)return '';return 'blue'}
function providerCard(p){var stats=[['原始',p.raw_events||0],['指标',p.metrics||0],['信号',p.signals||0]].map(function(x){return '<span class="badge">'+x[0]+' '+x[1]+'</span>'}).join('');var key=p.api_key_present?'<span class="badge mapped">Key 已配置</span>':'<span class="badge unmapped">无 Key</span>';var impl=p.implemented?'<span class="badge blue">已接入</span>':'<span class="badge warn">待接入</span>';var foot='主链上数据源:当前负责 EVM Transfer 日志、大额转账和 holder 变化。';return '<div class="source-card"><div class="source-top"><div class="source-name">'+esc(p.label||p.provider)+'</div><span class="badge '+statusBadgeClass(p.status)+'">'+esc(p.status||'--')+'</span></div><div class="source-role">'+esc(p.role||'--')+'</div><div class="source-stats">'+stats+key+impl+'</div><div class="source-foot">'+foot+'</div></div>'}
function renderProviderStatus(s){var providers=s.providers||[];$('providerStatus').innerHTML=providers.length?providers.map(providerCard).join(''):'<div class="empty">暂无数据源状态</div>';var c=s.coverage||{};var steps=[['原始流',c.raw_events||0],['币种映射',c.usable_mappings||0],['标准信号',c.signals||0],['技术检查候选',c.queued_candidates||0]];$('flowStatus').innerHTML=steps.map(function(x){return '<div class="flow-step"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')}
function renderKpis(k){var cells=[['原始流',k.raw_event_count||0,'blue'],['已映射原始流',k.raw_mapped_count||0,'green'],['映射币种',k.token_count||0,'blue'],['标准正向信号',k.positive_events||0,'green'],['标准风险信号',k.risk_events||0,'red'],['映射信号流',k.mapped_signal_count||0,'blue']];$('kpis').innerHTML=cells.map(function(x){return '<div class="kpi"><span>'+x[0]+'</span><b class="'+x[2]+'">'+x[1]+'</b></div>'}).join('')}
function tokenRow(t, risk){var cnt=t.mapped_event_count||t.event_count||0;var latest=t.latest_event_at?(' · 最近 '+fmtTime(t.latest_event_at)):'';var sub=esc(t.chain)+' · 映射事件 '+cnt+latest;return '<div class="token-row" onclick="loadDetail(\''+esc(t.symbol)+'\')"><div class="token-main"><div class="sym">'+esc(t.symbol)+'</div><div class="sub">'+sub+'</div></div><div class="score '+(risk?'risk':'')+'">'+Number(risk?t.risk_score:t.onchain_score||0).toFixed(0)+'</div></div>'}
function renderSpotlight(d){$('hotTokens').innerHTML=(d.hot_tokens||[]).length?(d.hot_tokens||[]).map(function(t){return tokenRow(t,false)}).join(''):'<div class="empty">暂无映射后的正向链上信号</div>';$('riskTokens').innerHTML=(d.risk_tokens||[]).length?(d.risk_tokens||[]).map(function(t){return tokenRow(t,true)}).join(''):'<div class="empty">暂无映射后的风险信号</div>'}
async function loadOverview(){try{var r=await fetch(API+'/api/onchain/overview?hours='+$('hoursSel').value);var d=await r.json();renderKpis(d.kpi||{});renderSpotlight(d);renderProviderStatus(d.provider_status||{})}catch(e){$('kpis').innerHTML='<div class="empty">链上总览加载失败</div>';$('providerStatus').innerHTML='<div class="empty">数据源状态加载失败</div>'}}
function renderRawCard(e){var mapped=e.mapping_status==='mapped';var desc=e.plain_summary||e.description||e.name||e.token_short||'链上源捕捉到新的 Token 动态';var amount=e.total_amount||e.amount||0;var link=e.url?'<a class="raw-link" href="'+esc(e.url)+'" target="_blank" rel="noopener">查看来源</a>':'';var pr=e.priority||'medium';var prBadge=pr==='low'?'<span class="badge">低优先级</span>':pr==='high'?'<span class="badge pos">高优先级</span>':'<span class="badge blue">中优先级</span>';return '<div class="raw-card '+esc(pr)+'"><div class="raw-top"><div class="raw-title">'+esc(e.event_label||e.title||e.event_type)+'</div><span class="badge '+(mapped?'mapped':'unmapped')+'">'+(mapped?'已映射':'未映射')+'</span></div><div class="raw-desc">'+esc(desc)+'<div class="raw-meaning">'+esc(e.why_matters||'需要映射和验证后才可能进入主链路。')+'</div></div><div class="raw-meta"><span class="badge">'+esc(e.chain||'--')+'</span><span class="badge">'+esc(e.mapped_symbol||e.token_short||'未知 Token')+'</span>'+prBadge+(amount?'<span class="badge">热度 '+fmtAmount(amount)+'</span>':'')+'<span class="sub">'+fmtTime(e.detected_at)+'</span>'+link+'</div><div class="raw-meaning">'+esc(e.pipeline_note||'未完成映射时仅作为原始观察。')+'</div></div>'}
async function reloadRawEvents(offset){state.rawOffset=offset||0;$('rawFeed').innerHTML='<div class="loading">加载中...</div>';try{var qs='hours='+$('hoursSel').value+'&limit='+state.rawLimit+'&offset='+state.rawOffset+'&chain='+encodeURIComponent($('rawChainSel').value)+'&mapping_status='+encodeURIComponent($('rawMapSel').value)+'&priority='+encodeURIComponent(state.rawPriority||'');var d=await (await fetch(API+'/api/onchain/raw-events?'+qs)).json();state.rawTotal=d.total||0;var items=d.items||[];var mode=state.rawPriority==='low'?'低优先级':state.rawPriority==='important'?'重要事件':'全部原始流';$('rawInfo').textContent=mode+' · 共 '+state.rawTotal+' 条,显示最近 '+items.length+' 条';$('rawFeed').innerHTML=items.length?items.map(renderRawCard).join(''):(state.rawPriority==='important'?'<div class="empty">暂无高价值链上事件。</div>':'<div class="empty">暂无原始链上流</div>')}catch(e){$('rawFeed').innerHTML='<div class="empty">原始链上流加载失败</div>';$('rawInfo').textContent='加载失败'}}
async function reloadTokens(offset){state.offset=offset||0;$('tokenTable').innerHTML='<tr><td colspan="8" class="loading">加载中...</td></tr>';try{var qs='hours='+$('hoursSel').value+'&limit='+state.limit+'&offset='+state.offset+'&chain='+encodeURIComponent($('chainSel').value)+'&signal='+encodeURIComponent($('signalSel').value);var d=await (await fetch(API+'/api/onchain/tokens?'+qs)).json();state.total=d.total||0;var items=d.items||[];if(!items.length){$('tokenTable').innerHTML='<tr><td colspan="8" class="empty">暂无链上异动 token</td></tr>'}else{$('tokenTable').innerHTML=items.map(function(t){return '<tr onclick="loadDetail(\''+esc(t.symbol)+'\')"><td class="sym">'+esc(t.symbol)+'</td><td>'+esc(t.chain)+'</td><td class="num">'+Number(t.onchain_score||0).toFixed(0)+'</td><td class="num">'+Number(t.risk_score||0).toFixed(0)+'</td><td class="num">'+Number(t.event_count||t.mapped_event_count||0).toFixed(0)+'</td><td>'+esc(t.latest_event_at?fmtTime(t.latest_event_at):'--')+'</td><td>'+esc(t.source||'nodereal')+'</td><td>'+recLabel(t.recommendation)+'</td></tr>'}).join('');if(!state.selected&&items[0])loadDetail(items[0].symbol)}updatePager()}catch(e){$('tokenTable').innerHTML='<tr><td colspan="8" class="empty">加载失败</td></tr>'}}
function updatePager(){var page=Math.floor(state.offset/state.limit)+1,totalPages=Math.max(1,Math.ceil((state.total||0)/state.limit));$('pageInfo').textContent='第 '+page+' / '+totalPages+' 页,共 '+state.total+' 个';$('prevBtn').disabled=state.offset<=0;$('nextBtn').disabled=state.offset+state.limit>=state.total}
function page(step){var next=state.offset+step*state.limit;if(next<0||next>=state.total)return;reloadTokens(next)}
async function loadDetail(symbol){state.selected=symbol;$('detailNote').textContent=symbol;$('detailBody').innerHTML='<div class="loading">加载详情...</div>';try{var d=await (await fetch(API+'/api/onchain/tokens/'+encodeURIComponent(symbol)+'?hours=168')).json();var latest=(d.metrics||[])[0]||{};var rec=d.recommendation;var metrics='<div class="metric-grid">'+[['链上分',Number(latest.onchain_score||0).toFixed(0)],['风险分',Number(latest.risk_score||0).toFixed(0)],['映射事件',Number(latest.event_count||latest.mapped_event_count||0).toFixed(0)],['数据源',latest.source||'nodereal']].map(function(x){return '<div class="metric"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')+'</div>';var recHtml=rec?'<div class="hint">主链路状态:'+esc(rec.action_status||rec.execution_status||'观察')+' · 推荐 #'+esc(rec.id)+'</div>':'<div class="hint">尚未形成主链路推荐;若链上信号质量足够,会先进入技术检查。</div>';var events=(d.events||[]).slice(0,20).map(function(e){var cls=e.direction==='risk'?'risk':e.direction==='positive'?'pos':'blue';return '<div class="event"><div class="event-top"><div class="event-title">'+esc(e.signal_label||e.signal_code)+'</div><span class="badge '+cls+'">'+esc(e.severity||e.direction)+'</span></div><div class="event-meta">'+fmtTime(e.detected_at)+' · '+esc(e.chain)+' · '+fmtUsd(e.value_usd)+'<br>'+esc(e.wallet_label||e.counterparty_label||e.source||'链上事件')+'</div></div>'}).join('')||'<div class="empty">暂无事件明细</div>';$('detailBody').innerHTML='<div class="detail-title">'+esc(d.symbol)+'</div><div class="detail-sub">'+(d.mappings||[]).length+' 个合约映射 · 近 7 天</div>'+metrics+recHtml+'<div class="event-feed">'+events+'</div>'}catch(e){$('detailBody').innerHTML='<div class="empty">详情加载失败</div>'}}
function reloadAll(){state.offset=0;state.rawOffset=0;state.selected='';loadOverview();reloadRawEvents(0);reloadTokens(0)}
reloadAll();
setInterval(reloadAll,300000);
</script>
{% endblock %}