103 lines
27 KiB
HTML
103 lines
27 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}AlphaX Agent — 链上观察{% endblock %}
|
|
{% block extra_head_css %}
|
|
<style>
|
|
.shell{width:min(100% - 40px,1320px);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:6px;color:var(--stone);font-size:13px;line-height:1.55;max-width:760px}.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:12px 14px;border:1px solid rgba(66,98,255,.14);background:linear-gradient(135deg,rgba(66,98,255,.06),rgba(0,180,115,.035));border-radius:var(--radius-md);color:var(--slate);font-size:12px;line-height:1.6;margin-bottom:14px}.kpis{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin-bottom:12px}.kpi{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);padding:14px;min-width:0;box-shadow:0 10px 30px rgba(15,23,42,.035)}.kpi span{display:block;color:var(--stone);font-size:11px;font-weight:900}.kpi b{display:block;margin-top:8px;color:var(--ink);font-size:24px;line-height:1;font-weight:950;letter-spacing:-.6px}.kpi b.green{color:var(--green)}.kpi b.red{color:var(--red)}.kpi b.blue{color:var(--blue)}.ops-grid{display:grid;grid-template-columns:1.1fr 1.9fr;gap:12px;margin-bottom:14px}.source-strip{display:grid;gap:10px}.source-card,.flow-step,.panel,.kpi{backdrop-filter:saturate(120%) blur(2px)}.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:28px}.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}.flow-step{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:11px}.flow-step span{display:block;color:var(--stone);font-size:10px;font-weight:900}.flow-step b{display:block;margin-top:6px;color:var(--ink);font-size:22px;font-weight:950}.intel-grid{display:grid;grid-template-columns:minmax(0,1.45fr) minmax(360px,.9fr);gap:14px;align-items:start;margin-bottom:14px}.signal-stack{display:grid;gap:12px}.spotlight{display:grid;gap:12px}.panel{border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);min-width:0;overflow:hidden;box-shadow:0 16px 38px rgba(15,23,42,.04)}.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-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(2,minmax(0,1fr));gap:10px;padding:12px}.raw-card{border:1px solid var(--hairline-soft);background:var(--surface);border-radius:var(--radius-md);padding:12px;min-width:0;transition:.15s}.raw-card:hover{transform:translateY(-1px);border-color:rgba(66,98,255,.22);background:rgba(66,98,255,.035)}.raw-card.low{opacity:.72}.raw-card.medium{border-color:rgba(66,98,255,.16)}.raw-top{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:8px}.raw-title{font-size:13px;font-weight:950;color:var(--ink);line-height:1.35;min-width:0}.raw-desc{color:var(--slate);font-size:13px;line-height:1.55;min-height:42px}.raw-meaning{margin-top:8px;color:var(--stone);font-size:11px;line-height:1.5}.raw-meta{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:10px}.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) 420px;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.5}.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}@media(max-width:1120px){.kpis{grid-template-columns:repeat(3,minmax(0,1fr))}.ops-grid,.intel-grid,.layout{grid-template-columns:1fr}.raw-feed{grid-template-columns:1fr 1fr}}@media(max-width:680px){.shell{width:min(100% - 24px,1320px)}.kpis{grid-template-columns:repeat(2,minmax(0,1fr))}.flow-strip{grid-template-columns:repeat(2,minmax(0,1fr))}.raw-feed{grid-template-columns:1fr}.page-head h1{font-size:22px}.metric-grid{grid-template-columns:1fr}}
|
|
.tabbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 14px;padding:6px;border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);box-shadow:0 12px 32px rgba(15,23,42,.035)}.tab-btn{height:36px;border:0;background:transparent;border-radius:10px;padding:0 13px;color:var(--stone);font-weight:950;font-size:12px;cursor:pointer}.tab-btn.active{background:var(--ink);color:var(--canvas);box-shadow:0 10px 24px rgba(15,23,42,.14)}.tab-page{display:none}.tab-page.active{display:block}.score{grid-template-rows:auto auto;gap:2px}.score b{font-size:15px;line-height:1}.score span{font-size:10px;font-weight:900;color:var(--stone);line-height:1}.score.risk span{color:var(--red)}.asset-grid{display:grid;grid-template-columns:minmax(340px,.8fr) minmax(0,1.4fr);gap:14px;align-items:start}.detail-shell{max-width:880px}.detail-shell .panel{min-height:420px}@media(max-width:1120px){.asset-grid{grid-template-columns:1fr}}@media(max-width:680px){.tabbar{position:sticky;top:0;z-index:6}.tab-btn{flex:1 1 calc(50% - 8px)}}
|
|
.flow-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}.flow-step{position:relative;border:1px solid var(--hairline-soft);background:var(--canvas);border-radius:var(--radius-md);padding:14px;min-height:102px;box-shadow:0 10px 30px rgba(15,23,42,.035)}.flow-step:not(:last-child)::after{content:"";position:absolute;right:-10px;top:50%;width:10px;border-top:2px solid rgba(66,98,255,.18)}.flow-step span{display:block;color:var(--stone);font-size:11px;font-weight:950}.flow-step b{display:block;margin-top:8px;color:var(--ink);font-size:24px;line-height:1;font-weight:950;letter-spacing:-.6px}.flow-step p{margin:8px 0 0;color:var(--stone);font-size:11px;line-height:1.45}.flow-step.good b{color:var(--green)}.flow-step.warn b{color:#8a5a00}.flow-step.blue b{color:var(--blue)}@media(max-width:680px){.flow-strip{grid-template-columns:1fr}.flow-step:not(:last-child)::after{display:none}}
|
|
</style>
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="shell">
|
|
<div class="page-head">
|
|
<div><h1>链上观察</h1><p>观察资金在链上的异常流动:大额转账、交易所流入流出、持有人变化和潜在风险。链上信号只提供线索,最终仍由价格结构确认。</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="tabbar">
|
|
<button class="tab-btn active" data-tab="overview" onclick="activateTab('overview')">总览</button>
|
|
<button class="tab-btn" data-tab="events" onclick="activateTab('events')">链上事件</button>
|
|
<button class="tab-btn" data-tab="assets" onclick="activateTab('assets')">相关币种</button>
|
|
</div>
|
|
<section class="tab-page active" id="tab-overview">
|
|
<div class="kpis" id="kpis"><div class="loading">加载中...</div></div>
|
|
<div class="ops-grid">
|
|
<div class="source-strip" id="providerStatus"><div class="loading">加载监控状态...</div></div>
|
|
<div class="flow-strip" id="flowStatus"></div>
|
|
</div>
|
|
</section>
|
|
<section class="tab-page" id="tab-events">
|
|
<section class="panel raw-panel">
|
|
<div class="panel-head"><div class="panel-title">重要链上事件</div><div class="panel-note">先看发生了什么,再决定是否值得跟踪</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>
|
|
</section>
|
|
<section class="tab-page" id="tab-assets">
|
|
<div class="asset-grid">
|
|
<div class="signal-stack">
|
|
<section class="panel"><div class="panel-head"><div class="panel-title">正向资金线索</div><div class="panel-note">已关联到交易所币种</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>
|
|
<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 class="layout" style="grid-template-columns:1fr;margin-top:14px">
|
|
<section class="panel">
|
|
<div class="panel-head"><div class="panel-title">相关币种列表</div><div class="panel-note">按币种筛选、翻页和进入单币档案</div></div>
|
|
<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>
|
|
</div>
|
|
</section>
|
|
</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 activateTab(name){document.querySelectorAll('.tab-page').forEach(function(el){el.classList.toggle('active',el.id==='tab-'+name)});document.querySelectorAll('.tab-btn').forEach(function(btn){btn.classList.toggle('active',btn.dataset.tab===name)})}
|
|
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 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(!isFinite(v)||v<=0)return'--';if(v>=1e9)return(v/1e9).toFixed(2)+'B';if(v>=1e6)return(v/1e6).toFixed(2)+'M';if(v>=1e3)return(v/1e3).toFixed(2)+'K';if(v>=1)return v.toFixed(2).replace(/\.?0+$/,'');return v.toFixed(6).replace(/\.?0+$/,'')}
|
|
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 foot='系统正在自动观察大额转账、资金流向和持有人变化。';return '<div class="source-card"><div class="source-top"><div class="source-name">链上监控</div><span class="badge '+statusBadgeClass(p.status)+'">'+esc(p.status||'--')+'</span></div><div class="source-role">资金流与风险观察</div><div class="source-stats">'+stats+'</div><div class="source-foot">'+foot+'</div></div>'}
|
|
function renderProviderStatus(s){var providers=s.providers||[];$('providerStatus').innerHTML=providers.length?providers.slice(0,1).map(providerCard).join(''):'<div class="empty">暂无监控状态</div>';var c=s.coverage||{};var steps=[['捕捉事件',c.raw_events||0,'系统看到的链上动态。','blue'],['关联币种',c.usable_mappings||0,'能对应到交易所币种的资产。','good'],['有效线索',c.signals||0,'整理后的正向或风险线索。','warn'],['进入机会检查',c.queued_candidates||0,'已经交给价格结构继续确认。','blue']];$('flowStatus').innerHTML=steps.map(function(x){return '<div class="flow-step '+x[3]+'"><span>'+x[0]+'</span><b>'+x[1]+'</b><p>'+x[2]+'</p></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;var label=risk?'风险':'重要性';var score=Number(risk?t.risk_score:t.onchain_score||0).toFixed(0);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':'')+'"><b>'+score+'</b><span>'+label+'</span></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.human_summary||e.description||e.plain_summary||'链上源捕捉到一条新动态';var amount=e.display_amount_label||fmtAmount(e.display_amount||e.total_amount||e.amount);var link=e.url?'<a class="raw-link" href="'+esc(e.url)+'" target="_blank" rel="noopener">查看来源</a>':'';var pr=e.priority||'medium';var prBadge=Number(e.importance||0)>=90?'<span class="badge pos">高重要性</span>':Number(e.importance||0)>=70?'<span class="badge blue">重要</span>':'<span class="badge">低优先级</span>';var route=(e.from_short&&e.to_short)?'<span class="badge">'+esc(e.from_short)+' → '+esc(e.to_short)+'</span>':'';return '<div class="raw-card '+esc(pr)+'"><div class="raw-top"><div class="raw-title">'+esc(e.mapped_symbol||e.symbol_guess||e.event_label||'链上事件')+'</div><span class="badge '+(mapped?'mapped':'unmapped')+'">'+(mapped?'已映射':'未映射')+'</span></div><div class="raw-desc">'+esc(desc)+'</div><div class="raw-meta"><span class="badge">'+esc(e.chain||'--')+'</span><span class="badge">'+esc(amount)+'</span>'+route+prBadge+'<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">暂无相关币种</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 rawCount=Number(d.raw_event_count||latest.event_count||latest.mapped_event_count||0);var metrics='<div class="metric-grid">'+[['重要性',Number(latest.onchain_score||0).toFixed(0)],['风险分',Number(latest.risk_score||0).toFixed(0)],['映射事件',rawCount.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 standardEvents=(d.events||[]).slice(0,10).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>'});var rawEvents=(d.raw_events||[]).slice(0,20).map(function(e){var amount=e.display_amount_label||fmtAmount(e.display_amount||e.total_amount||e.amount);var link=e.url?' · <a class="raw-link" href="'+esc(e.url)+'" target="_blank" rel="noopener">查看来源</a>':'';var route=(e.from_short&&e.to_short)?'<br>路径:'+esc(e.from_short)+' → '+esc(e.to_short):'';return '<div class="event"><div class="event-top"><div class="event-title">'+esc(e.human_summary||e.event_label||'NodeReal 原始事件')+'</div><span class="badge mapped">已映射</span></div><div class="event-meta">'+fmtTime(e.detected_at)+' · '+esc(e.chain)+' · '+esc(amount)+link+route+'<br>'+esc(e.pipeline_note||'已映射,可进入后续链上信号分析。')+'</div></div>'});var events=standardEvents.concat(rawEvents).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 %}
|