1790 lines
72 KiB
HTML
1790 lines
72 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>NAC Lens | NewAssetChain 量子区块浏览器</title>
|
||
<meta name="description" content="NAC NewAssetChain 量子区块浏览器 - 实时查看 NAC 公链区块、交易、地址和 RWA 资产数据">
|
||
<!-- Bootstrap 5 - 国内 CDN -->
|
||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css">
|
||
<!-- Bootstrap Icons -->
|
||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css">
|
||
<style>
|
||
:root {
|
||
--nac-bg: #030b18;
|
||
--nac-card: #0d1b2e;
|
||
--nac-card2: #0f2040;
|
||
--nac-border: #1a3050;
|
||
--nac-primary: #00c8ff;
|
||
--nac-green: #00e676;
|
||
--nac-yellow: #ffd740;
|
||
--nac-red: #ff5252;
|
||
--nac-text: #e2e8f0;
|
||
--nac-muted: #7a9bbf;
|
||
--nac-link: #00c8ff;
|
||
--nac-accent: #7c3aed;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
background: var(--nac-bg);
|
||
color: var(--nac-text);
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
min-height: 100vh;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* ── Navbar ── */
|
||
.navbar-nac {
|
||
background: rgba(3, 11, 24, 0.97);
|
||
border-bottom: 1px solid var(--nac-border);
|
||
padding: 0.6rem 0;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1000;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
.navbar-brand {
|
||
font-size: 1.2rem;
|
||
font-weight: 700;
|
||
color: var(--nac-primary) !important;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.navbar-brand .brand-sub {
|
||
font-size: 0.65rem;
|
||
color: var(--nac-muted);
|
||
display: block;
|
||
letter-spacing: 1px;
|
||
text-transform: uppercase;
|
||
}
|
||
.nav-link {
|
||
color: var(--nac-muted) !important;
|
||
font-size: 0.85rem;
|
||
padding: 0.4rem 0.8rem !important;
|
||
transition: color 0.2s;
|
||
}
|
||
.nav-link:hover, .nav-link.active {
|
||
color: var(--nac-primary) !important;
|
||
}
|
||
|
||
/* ── Search Bar ── */
|
||
.search-wrapper {
|
||
position: relative;
|
||
max-width: 500px;
|
||
}
|
||
.search-input {
|
||
background: var(--nac-card2);
|
||
border: 1px solid var(--nac-border);
|
||
color: var(--nac-text);
|
||
border-radius: 8px;
|
||
padding: 0.45rem 2.5rem 0.45rem 1rem;
|
||
font-size: 0.85rem;
|
||
width: 100%;
|
||
transition: border-color 0.2s, box-shadow 0.2s;
|
||
}
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: var(--nac-primary);
|
||
box-shadow: 0 0 0 3px rgba(0, 200, 255, 0.1);
|
||
background: var(--nac-card2);
|
||
color: var(--nac-text);
|
||
}
|
||
.search-input::placeholder { color: var(--nac-muted); }
|
||
.search-btn {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: none;
|
||
border: none;
|
||
color: var(--nac-muted);
|
||
cursor: pointer;
|
||
padding: 0;
|
||
font-size: 1rem;
|
||
}
|
||
.search-btn:hover { color: var(--nac-primary); }
|
||
|
||
/* ── Hero Stats ── */
|
||
.hero-stats {
|
||
background: linear-gradient(135deg, #030b18 0%, #0a1628 50%, #030b18 100%);
|
||
border-bottom: 1px solid var(--nac-border);
|
||
padding: 1.5rem 0;
|
||
}
|
||
.stat-card {
|
||
background: var(--nac-card);
|
||
border: 1px solid var(--nac-border);
|
||
border-radius: 10px;
|
||
padding: 1rem 1.2rem;
|
||
transition: border-color 0.2s;
|
||
}
|
||
.stat-card:hover { border-color: var(--nac-primary); }
|
||
.stat-label {
|
||
font-size: 0.72rem;
|
||
color: var(--nac-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
.stat-value {
|
||
font-size: 1.3rem;
|
||
font-weight: 700;
|
||
color: var(--nac-text);
|
||
line-height: 1.2;
|
||
}
|
||
.stat-sub {
|
||
font-size: 0.72rem;
|
||
color: var(--nac-muted);
|
||
margin-top: 0.2rem;
|
||
}
|
||
.stat-icon {
|
||
font-size: 1.5rem;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* ── Cards ── */
|
||
.nac-card {
|
||
background: var(--nac-card);
|
||
border: 1px solid var(--nac-border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
.nac-card-header {
|
||
background: var(--nac-card2);
|
||
border-bottom: 1px solid var(--nac-border);
|
||
padding: 0.8rem 1.2rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.nac-card-title {
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
color: var(--nac-text);
|
||
margin: 0;
|
||
}
|
||
.view-all-btn {
|
||
font-size: 0.78rem;
|
||
color: var(--nac-primary);
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
}
|
||
.view-all-btn:hover { text-decoration: underline; color: var(--nac-primary); }
|
||
|
||
/* ── Table ── */
|
||
.nac-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
.nac-table th {
|
||
font-size: 0.72rem;
|
||
color: var(--nac-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
padding: 0.6rem 1rem;
|
||
border-bottom: 1px solid var(--nac-border);
|
||
font-weight: 500;
|
||
background: var(--nac-card2);
|
||
}
|
||
.nac-table td {
|
||
padding: 0.65rem 1rem;
|
||
border-bottom: 1px solid rgba(26, 48, 80, 0.5);
|
||
font-size: 0.83rem;
|
||
vertical-align: middle;
|
||
}
|
||
.nac-table tr:last-child td { border-bottom: none; }
|
||
.nac-table tr:hover td { background: rgba(0, 200, 255, 0.03); }
|
||
|
||
/* ── Links ── */
|
||
.nac-link {
|
||
color: var(--nac-link);
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
}
|
||
.nac-link:hover { text-decoration: underline; color: var(--nac-link); }
|
||
|
||
/* ── Hash display ── */
|
||
.hash-short {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.82rem;
|
||
}
|
||
.hash-full {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.78rem;
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* ── Badges ── */
|
||
.badge-success { background: rgba(0, 230, 118, 0.15); color: var(--nac-green); border: 1px solid rgba(0, 230, 118, 0.3); }
|
||
.badge-info { background: rgba(0, 200, 255, 0.15); color: var(--nac-primary); border: 1px solid rgba(0, 200, 255, 0.3); }
|
||
.badge-warning { background: rgba(255, 215, 64, 0.15); color: var(--nac-yellow); border: 1px solid rgba(255, 215, 64, 0.3); }
|
||
.badge-purple { background: rgba(124, 58, 237, 0.15); color: #a78bfa; border: 1px solid rgba(124, 58, 237, 0.3); }
|
||
.badge-nac {
|
||
font-size: 0.7rem;
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ── Pages ── */
|
||
.page { display: none; }
|
||
.page.active { display: block; }
|
||
|
||
/* ── Detail Page ── */
|
||
.detail-header {
|
||
background: var(--nac-card);
|
||
border: 1px solid var(--nac-border);
|
||
border-radius: 10px;
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
.detail-title {
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
color: var(--nac-text);
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.detail-row {
|
||
display: flex;
|
||
padding: 0.7rem 0;
|
||
border-bottom: 1px solid rgba(26, 48, 80, 0.5);
|
||
align-items: flex-start;
|
||
}
|
||
.detail-row:last-child { border-bottom: none; }
|
||
.detail-label {
|
||
width: 200px;
|
||
min-width: 200px;
|
||
font-size: 0.82rem;
|
||
color: var(--nac-muted);
|
||
padding-top: 0.1rem;
|
||
}
|
||
.detail-value {
|
||
flex: 1;
|
||
font-size: 0.85rem;
|
||
color: var(--nac-text);
|
||
word-break: break-all;
|
||
}
|
||
|
||
/* ── Tabs ── */
|
||
.nac-tabs {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--nac-border);
|
||
margin-bottom: 0;
|
||
background: var(--nac-card2);
|
||
border-radius: 10px 10px 0 0;
|
||
overflow: hidden;
|
||
}
|
||
.nac-tab {
|
||
padding: 0.75rem 1.2rem;
|
||
font-size: 0.83rem;
|
||
color: var(--nac-muted);
|
||
cursor: pointer;
|
||
border-bottom: 2px solid transparent;
|
||
transition: all 0.2s;
|
||
white-space: nowrap;
|
||
background: none;
|
||
border-top: none;
|
||
border-left: none;
|
||
border-right: none;
|
||
}
|
||
.nac-tab:hover { color: var(--nac-text); }
|
||
.nac-tab.active {
|
||
color: var(--nac-primary);
|
||
border-bottom-color: var(--nac-primary);
|
||
background: rgba(0, 200, 255, 0.05);
|
||
}
|
||
.tab-content { display: none; }
|
||
.tab-content.active { display: block; }
|
||
|
||
/* ── Pagination ── */
|
||
.nac-pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
justify-content: center;
|
||
padding: 1rem;
|
||
}
|
||
.page-btn {
|
||
background: var(--nac-card2);
|
||
border: 1px solid var(--nac-border);
|
||
color: var(--nac-muted);
|
||
padding: 0.3rem 0.7rem;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 0.8rem;
|
||
transition: all 0.2s;
|
||
}
|
||
.page-btn:hover, .page-btn.active {
|
||
background: var(--nac-primary);
|
||
border-color: var(--nac-primary);
|
||
color: #000;
|
||
}
|
||
.page-btn:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* ── Loading ── */
|
||
.loading-spinner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 2rem;
|
||
color: var(--nac-muted);
|
||
gap: 0.5rem;
|
||
}
|
||
.spinner {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 2px solid var(--nac-border);
|
||
border-top-color: var(--nac-primary);
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
/* ── Empty State ── */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 3rem 1rem;
|
||
color: var(--nac-muted);
|
||
}
|
||
.empty-state i { font-size: 2.5rem; opacity: 0.4; margin-bottom: 0.8rem; }
|
||
|
||
/* ── Copy button ── */
|
||
.copy-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--nac-muted);
|
||
cursor: pointer;
|
||
padding: 0 0.3rem;
|
||
font-size: 0.85rem;
|
||
transition: color 0.2s;
|
||
}
|
||
.copy-btn:hover { color: var(--nac-primary); }
|
||
|
||
/* ── Toast ── */
|
||
.toast-container {
|
||
position: fixed;
|
||
bottom: 1.5rem;
|
||
right: 1.5rem;
|
||
z-index: 9999;
|
||
}
|
||
.nac-toast {
|
||
background: var(--nac-card2);
|
||
border: 1px solid var(--nac-border);
|
||
border-radius: 8px;
|
||
padding: 0.7rem 1rem;
|
||
font-size: 0.82rem;
|
||
color: var(--nac-text);
|
||
margin-top: 0.5rem;
|
||
animation: slideIn 0.3s ease;
|
||
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
|
||
}
|
||
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||
|
||
/* ── Footer ── */
|
||
.nac-footer {
|
||
background: var(--nac-card);
|
||
border-top: 1px solid var(--nac-border);
|
||
padding: 1.5rem 0;
|
||
margin-top: 3rem;
|
||
font-size: 0.8rem;
|
||
color: var(--nac-muted);
|
||
}
|
||
|
||
/* ── Responsive ── */
|
||
@media (max-width: 768px) {
|
||
.detail-label { width: 130px; min-width: 130px; }
|
||
.nac-table th:nth-child(n+4),
|
||
.nac-table td:nth-child(n+4) { display: none; }
|
||
.hero-stats .col-md-2 { margin-bottom: 0.5rem; }
|
||
}
|
||
|
||
/* ── Pulse animation for live data ── */
|
||
.live-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
background: var(--nac-green);
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
animation: pulse 2s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.5; transform: scale(0.8); }
|
||
}
|
||
|
||
/* ── Address icon ── */
|
||
.addr-icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ── Block type indicator ── */
|
||
.block-type-tx { color: var(--nac-green); }
|
||
.block-type-hb { color: var(--nac-muted); }
|
||
|
||
/* ── Breadcrumb ── */
|
||
.nac-breadcrumb {
|
||
font-size: 0.8rem;
|
||
color: var(--nac-muted);
|
||
margin-bottom: 1rem;
|
||
}
|
||
.nac-breadcrumb a { color: var(--nac-link); text-decoration: none; cursor: pointer; }
|
||
.nac-breadcrumb a:hover { text-decoration: underline; }
|
||
.nac-breadcrumb .sep { margin: 0 0.4rem; }
|
||
|
||
/* ── Search result dropdown ── */
|
||
.search-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
background: var(--nac-card2);
|
||
border: 1px solid var(--nac-border);
|
||
border-radius: 0 0 8px 8px;
|
||
z-index: 100;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
display: none;
|
||
}
|
||
.search-dropdown.show { display: block; }
|
||
.search-result-item {
|
||
padding: 0.6rem 1rem;
|
||
cursor: pointer;
|
||
border-bottom: 1px solid rgba(26, 48, 80, 0.5);
|
||
font-size: 0.82rem;
|
||
}
|
||
.search-result-item:hover { background: rgba(0, 200, 255, 0.05); }
|
||
.search-result-item:last-child { border-bottom: none; }
|
||
.search-result-type {
|
||
font-size: 0.68rem;
|
||
color: var(--nac-muted);
|
||
text-transform: uppercase;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ── Navbar ── -->
|
||
<nav class="navbar-nac">
|
||
<div class="container-fluid px-3">
|
||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||
<!-- Brand -->
|
||
<a class="navbar-brand d-flex align-items-center gap-2" href="#" onclick="navigate('home')">
|
||
<div>
|
||
<div>NAC Lens</div>
|
||
<span class="brand-sub">NewAssetChain Explorer</span>
|
||
</div>
|
||
</a>
|
||
|
||
<!-- Nav Links -->
|
||
<div class="d-none d-md-flex align-items-center gap-1">
|
||
<a class="nav-link" onclick="navigate('blocks')"><i class="bi bi-grid-3x3-gap me-1"></i>区块</a>
|
||
<a class="nav-link" onclick="navigate('transactions')"><i class="bi bi-arrow-left-right me-1"></i>交易</a>
|
||
<a class="nav-link" onclick="navigate('assets')"><i class="bi bi-gem me-1"></i>RWA 资产</a>
|
||
<a class="nav-link" onclick="navigate('stats')"><i class="bi bi-bar-chart me-1"></i>统计</a>
|
||
</div>
|
||
|
||
<!-- Search -->
|
||
<div class="search-wrapper">
|
||
<input type="text" class="search-input" id="globalSearch"
|
||
placeholder="搜索区块号 / 交易哈希 / 地址..."
|
||
onkeydown="if(event.key==='Enter') doSearch()"
|
||
oninput="onSearchInput(this.value)">
|
||
<button class="search-btn" onclick="doSearch()"><i class="bi bi-search"></i></button>
|
||
<div class="search-dropdown" id="searchDropdown"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ── Toast Container ── -->
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
|
||
<!-- ── Main Content ── -->
|
||
<div id="app">
|
||
|
||
<!-- ══ HOME PAGE ══ -->
|
||
<div id="page-home" class="page active">
|
||
<!-- Hero Stats -->
|
||
<div class="hero-stats">
|
||
<div class="container-fluid px-3">
|
||
<div class="row g-2" id="statsRow">
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-stack me-1"></i>最新区块</div>
|
||
<div class="stat-value" id="stat-block">--</div>
|
||
<div class="stat-sub">CBPP 共识</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-arrow-left-right me-1"></i>总交易数</div>
|
||
<div class="stat-value" id="stat-txs">--</div>
|
||
<div class="stat-sub">预估值</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-cpu me-1"></i>NVM 版本</div>
|
||
<div class="stat-value" id="stat-nvm">--</div>
|
||
<div class="stat-sub">NAC 虚拟机</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-shield-check me-1"></i>共识协议</div>
|
||
<div class="stat-value" id="stat-consensus">--</div>
|
||
<div class="stat-sub">宪政区块生产</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-link-45deg me-1"></i>链 ID</div>
|
||
<div class="stat-value" id="stat-chainid">--</div>
|
||
<div class="stat-sub">主网</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="stat-card">
|
||
<div class="stat-label"><i class="bi bi-circle-fill me-1" style="color:var(--nac-green);font-size:0.5rem"></i>网络状态</div>
|
||
<div class="stat-value" id="stat-status" style="color:var(--nac-green)">--</div>
|
||
<div class="stat-sub" id="stat-mode">--</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Latest Blocks & Transactions -->
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="row g-3">
|
||
<!-- Latest Blocks -->
|
||
<div class="col-lg-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-grid-3x3-gap me-2" style="color:var(--nac-primary)"></i>最新区块</span>
|
||
<button class="view-all-btn" onclick="navigate('blocks')">查看全部 <i class="bi bi-arrow-right"></i></button>
|
||
</div>
|
||
<div id="latestBlocksList">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Latest Transactions -->
|
||
<div class="col-lg-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-arrow-left-right me-2" style="color:var(--nac-yellow)"></i>最新交易</span>
|
||
<button class="view-all-btn" onclick="navigate('transactions')">查看全部 <i class="bi bi-arrow-right"></i></button>
|
||
</div>
|
||
<div id="latestTxsList">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- NAC Tech Info -->
|
||
<div class="row g-3 mt-1">
|
||
<div class="col-12">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-info-circle me-2" style="color:var(--nac-accent)"></i>NAC 原生技术栈</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="row g-2">
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">智能合约语言</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">Charter</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">非 Solidity</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">虚拟机</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">NVM</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">NAC Virtual Machine</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">共识协议</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">CBPP</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">宪政区块生产协议</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">网络协议</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">CSNP</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">合规安全网络协议</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">代币标准</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">ACC-20</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">内置合规验证</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">稳定币</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">XTZH</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">SDR 锚定 + 黄金储备</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">神经网络语言</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">CNNL</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">宪政神经网络语言</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);text-transform:uppercase;margin-bottom:0.3rem;">资产分类</div>
|
||
<div style="font-weight:600;color:var(--nac-primary)">GNACS</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">全球 NAC 资产分类系统</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ BLOCKS PAGE ══ -->
|
||
<div id="page-blocks" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<span>区块列表</span>
|
||
</div>
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-grid-3x3-gap me-2" style="color:var(--nac-primary)"></i>区块列表</span>
|
||
<span style="font-size:0.78rem;color:var(--nac-muted)"><span class="live-dot me-1"></span>实时更新</span>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead>
|
||
<tr>
|
||
<th>区块高度</th>
|
||
<th>区块哈希</th>
|
||
<th>时间</th>
|
||
<th>交易数</th>
|
||
<th>类型</th>
|
||
<th>出块人</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="blocksTableBody">
|
||
<tr><td colspan="6"><div class="loading-spinner"><div class="spinner"></div>加载中...</div></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="nac-pagination" id="blocksPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ TRANSACTIONS PAGE ══ -->
|
||
<div id="page-transactions" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<span>交易列表</span>
|
||
</div>
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-arrow-left-right me-2" style="color:var(--nac-yellow)"></i>交易列表</span>
|
||
<span style="font-size:0.78rem;color:var(--nac-muted)">最近 200 个区块</span>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead>
|
||
<tr>
|
||
<th>交易哈希</th>
|
||
<th>区块</th>
|
||
<th>时间</th>
|
||
<th>发送方</th>
|
||
<th>接收方</th>
|
||
<th>金额</th>
|
||
<th>状态</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="txsTableBody">
|
||
<tr><td colspan="7"><div class="loading-spinner"><div class="spinner"></div>加载中...</div></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="nac-pagination" id="txsPagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ BLOCK DETAIL PAGE ══ -->
|
||
<div id="page-block-detail" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<a onclick="navigate('blocks')">区块列表</a>
|
||
<span class="sep">/</span>
|
||
<span id="blockDetailBreadcrumb">区块详情</span>
|
||
</div>
|
||
<div id="blockDetailContent">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ TX DETAIL PAGE ══ -->
|
||
<div id="page-tx-detail" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<a onclick="navigate('transactions')">交易列表</a>
|
||
<span class="sep">/</span>
|
||
<span>交易详情</span>
|
||
</div>
|
||
<div id="txDetailContent">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ ADDRESS DETAIL PAGE ══ -->
|
||
<div id="page-address-detail" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<span>地址详情</span>
|
||
</div>
|
||
<div id="addressDetailContent">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ ASSETS PAGE ══ -->
|
||
<div id="page-assets" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<span>RWA 资产</span>
|
||
</div>
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-gem me-2" style="color:var(--nac-accent)"></i>RWA 资产列表</span>
|
||
<span style="font-size:0.78rem;color:var(--nac-muted)">ACC-20 协议</span>
|
||
</div>
|
||
<div id="assetsContent">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══ STATS PAGE ══ -->
|
||
<div id="page-stats" class="page">
|
||
<div class="container-fluid px-3 py-3">
|
||
<div class="nac-breadcrumb">
|
||
<a onclick="navigate('home')">首页</a>
|
||
<span class="sep">/</span>
|
||
<span>网络统计</span>
|
||
</div>
|
||
<div id="statsContent">
|
||
<div class="loading-spinner"><div class="spinner"></div>加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /#app -->
|
||
|
||
<!-- ── Footer ── -->
|
||
<footer class="nac-footer">
|
||
<div class="container-fluid px-3">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div style="font-weight:600;color:var(--nac-text);margin-bottom:0.3rem;">NAC Lens | NewAssetChain 量子区块浏览器</div>
|
||
<div>NAC 公链 - 全球首个 RWA 原生公链,内置 AI 合规审批</div>
|
||
</div>
|
||
<div class="col-md-6 text-md-end mt-2 mt-md-0">
|
||
<div>协议:NAC Lens/6.0 | 共识:CBPP | 合约语言:Charter</div>
|
||
<div style="margin-top:0.3rem;">
|
||
<a href="https://newassetchain.io" target="_blank" style="color:var(--nac-link);margin-right:1rem;">官网</a>
|
||
<a href="https://api.newassetchain.io" target="_blank" style="color:var(--nac-link);margin-right:1rem;">API</a>
|
||
<a href="https://chat.newassetchain.io" target="_blank" style="color:var(--nac-link);">AI 助手</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<!-- Bootstrap JS -->
|
||
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js"></script>
|
||
|
||
<script>
|
||
// ═══════════════════════════════════════════════════════════
|
||
// NAC Lens SPA - 核心逻辑
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
const API_BASE = '/api/v1';
|
||
let currentPage = 'home';
|
||
let blocksPage = 1;
|
||
let txsPage = 1;
|
||
const PAGE_SIZE = 25;
|
||
let searchTimer = null;
|
||
|
||
// ── 路由 ──────────────────────────────────────────────────
|
||
function navigate(page, params = {}) {
|
||
// 隐藏所有页面
|
||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||
const el = document.getElementById('page-' + page);
|
||
if (!el) return;
|
||
el.classList.add('active');
|
||
currentPage = page;
|
||
|
||
// 更新 URL hash
|
||
let hash = '#/' + page;
|
||
if (params.id) hash += '/' + params.id;
|
||
history.pushState({ page, params }, '', hash);
|
||
|
||
// 加载数据
|
||
switch(page) {
|
||
case 'home': loadHome(); break;
|
||
case 'blocks': loadBlocks(); break;
|
||
case 'transactions': loadTransactions(); break;
|
||
case 'block-detail': loadBlockDetail(params.id); break;
|
||
case 'tx-detail': loadTxDetail(params.id); break;
|
||
case 'address-detail': loadAddressDetail(params.id); break;
|
||
case 'assets': loadAssets(); break;
|
||
case 'stats': loadStats(); break;
|
||
}
|
||
|
||
window.scrollTo(0, 0);
|
||
}
|
||
|
||
// 处理浏览器后退
|
||
window.addEventListener('popstate', (e) => {
|
||
if (e.state) navigate(e.state.page, e.state.params || {});
|
||
else navigate('home');
|
||
});
|
||
|
||
// ── API 请求 ──────────────────────────────────────────────
|
||
async function api(path) {
|
||
try {
|
||
const res = await fetch(API_BASE + path);
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
const json = await res.json();
|
||
return json.data || json;
|
||
} catch(e) {
|
||
console.error('API error:', path, e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ── 工具函数 ──────────────────────────────────────────────
|
||
function shortHash(h, len = 8) {
|
||
if (!h || h === '0'.repeat(64)) return '0x' + '0'.repeat(8) + '...';
|
||
const s = h.startsWith('0x') ? h : '0x' + h;
|
||
if (s.length <= len * 2 + 2) return s;
|
||
return s.slice(0, len + 2) + '...' + s.slice(-6);
|
||
}
|
||
|
||
function shortAddr(a, len = 8) {
|
||
if (!a) return '--';
|
||
const s = a.startsWith('0x') ? a : '0x' + a;
|
||
if (s.length <= 20) return s;
|
||
return s.slice(0, len + 2) + '...' + s.slice(-6);
|
||
}
|
||
|
||
function timeAgo(ts) {
|
||
if (!ts) return '--';
|
||
const now = Math.floor(Date.now() / 1000);
|
||
const diff = now - ts;
|
||
if (diff < 60) return diff + ' 秒前';
|
||
if (diff < 3600) return Math.floor(diff / 60) + ' 分钟前';
|
||
if (diff < 86400) return Math.floor(diff / 3600) + ' 小时前';
|
||
return Math.floor(diff / 86400) + ' 天前';
|
||
}
|
||
|
||
function formatTime(ts) {
|
||
if (!ts) return '--';
|
||
return new Date(ts * 1000).toLocaleString('zh-CN', {
|
||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||
hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||
});
|
||
}
|
||
|
||
function formatNum(n) {
|
||
if (n == null) return '--';
|
||
return Number(n).toLocaleString('zh-CN');
|
||
}
|
||
|
||
function addrColor(addr) {
|
||
if (!addr) return '#666';
|
||
const colors = ['#00c8ff','#00e676','#ffd740','#ff5252','#a78bfa','#f97316','#ec4899'];
|
||
let h = 0;
|
||
for (let i = 0; i < addr.length; i++) h = (h * 31 + addr.charCodeAt(i)) & 0xffffffff;
|
||
return colors[Math.abs(h) % colors.length];
|
||
}
|
||
|
||
function addrIcon(addr) {
|
||
const color = addrColor(addr);
|
||
const letter = addr ? addr.slice(-2).toUpperCase() : '??';
|
||
return `<div class="addr-icon" style="background:${color}22;color:${color};border:1px solid ${color}44">${letter}</div>`;
|
||
}
|
||
|
||
function copyText(text) {
|
||
navigator.clipboard.writeText(text).then(() => showToast('已复制到剪贴板'));
|
||
}
|
||
|
||
function showToast(msg, type = 'info') {
|
||
const container = document.getElementById('toastContainer');
|
||
const toast = document.createElement('div');
|
||
toast.className = 'nac-toast';
|
||
const icon = type === 'success' ? '✓' : type === 'error' ? '✗' : 'ℹ';
|
||
toast.innerHTML = `${icon} ${msg}`;
|
||
container.appendChild(toast);
|
||
setTimeout(() => toast.remove(), 3000);
|
||
}
|
||
|
||
// ── 搜索 ──────────────────────────────────────────────────
|
||
function onSearchInput(val) {
|
||
clearTimeout(searchTimer);
|
||
const dd = document.getElementById('searchDropdown');
|
||
if (!val.trim() || val.length < 3) { dd.classList.remove('show'); return; }
|
||
searchTimer = setTimeout(() => quickSearch(val.trim()), 300);
|
||
}
|
||
|
||
async function quickSearch(q) {
|
||
const data = await api('/search?q=' + encodeURIComponent(q));
|
||
const dd = document.getElementById('searchDropdown');
|
||
if (!data || data.error) { dd.classList.remove('show'); return; }
|
||
|
||
let html = '';
|
||
if (data.type === 'block') {
|
||
html = `<div class="search-result-item" onclick="navigate('block-detail',{id:'${data.data.number}'})">
|
||
<div class="search-result-type">区块</div>
|
||
<div class="nac-link">#${data.data.number}</div>
|
||
</div>`;
|
||
} else if (data.type === 'transaction') {
|
||
html = `<div class="search-result-item" onclick="navigate('tx-detail',{id:'${data.data.hash}'})">
|
||
<div class="search-result-type">交易</div>
|
||
<div class="nac-link hash-short">${shortHash(data.data.hash)}</div>
|
||
</div>`;
|
||
} else if (data.type === 'address') {
|
||
html = `<div class="search-result-item" onclick="navigate('address-detail',{id:'${data.data.address}'})">
|
||
<div class="search-result-type">地址</div>
|
||
<div class="nac-link hash-short">${shortAddr(data.data.address)}</div>
|
||
</div>`;
|
||
}
|
||
|
||
if (html) {
|
||
dd.innerHTML = html;
|
||
dd.classList.add('show');
|
||
} else {
|
||
dd.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
async function doSearch() {
|
||
const q = document.getElementById('globalSearch').value.trim();
|
||
document.getElementById('searchDropdown').classList.remove('show');
|
||
if (!q) return;
|
||
|
||
// 纯数字 → 区块
|
||
if (/^\d+$/.test(q)) {
|
||
navigate('block-detail', { id: q });
|
||
return;
|
||
}
|
||
|
||
// 哈希或地址
|
||
const data = await api('/search?q=' + encodeURIComponent(q));
|
||
if (!data || data.error) {
|
||
showToast('未找到匹配结果', 'error');
|
||
return;
|
||
}
|
||
|
||
if (data.type === 'block') navigate('block-detail', { id: data.data.number });
|
||
else if (data.type === 'transaction') navigate('tx-detail', { id: data.data.hash || q });
|
||
else if (data.type === 'address') navigate('address-detail', { id: data.data.address || q });
|
||
else showToast('未找到匹配结果', 'error');
|
||
}
|
||
|
||
// 点击其他地方关闭搜索下拉
|
||
document.addEventListener('click', (e) => {
|
||
if (!e.target.closest('.search-wrapper')) {
|
||
document.getElementById('searchDropdown').classList.remove('show');
|
||
}
|
||
});
|
||
|
||
// ── HOME ──────────────────────────────────────────────────
|
||
async function loadHome() {
|
||
const [stats, blocks, txs] = await Promise.all([
|
||
api('/network/stats'),
|
||
api('/blocks?limit=8'),
|
||
api('/transactions/latest?limit=8')
|
||
]);
|
||
|
||
// Stats
|
||
if (stats) {
|
||
document.getElementById('stat-block').textContent = formatNum(stats.blockHeight);
|
||
document.getElementById('stat-txs').textContent = formatNum(stats.estimatedTotalTxs);
|
||
document.getElementById('stat-nvm').textContent = 'v' + (stats.nvmVersion || '2.0');
|
||
document.getElementById('stat-consensus').textContent = stats.consensus || 'CBPP';
|
||
document.getElementById('stat-chainid').textContent = stats.chainIdHex || '--';
|
||
document.getElementById('stat-status').textContent = stats.cbppConsensus === 'active' ? '运行中' : '--';
|
||
document.getElementById('stat-mode').textContent = stats.blockProductionMode || '--';
|
||
}
|
||
|
||
// Latest Blocks
|
||
const blocksEl = document.getElementById('latestBlocksList');
|
||
if (blocks && blocks.blocks && blocks.blocks.length > 0) {
|
||
blocksEl.innerHTML = blocks.blocks.map(b => `
|
||
<div style="display:flex;align-items:center;padding:0.7rem 1rem;border-bottom:1px solid rgba(26,48,80,0.5);">
|
||
<div style="width:36px;height:36px;background:rgba(0,200,255,0.1);border:1px solid rgba(0,200,255,0.2);border-radius:8px;display:flex;align-items:center;justify-content:center;margin-right:0.8rem;flex-shrink:0;">
|
||
<i class="bi bi-grid-3x3" style="color:var(--nac-primary);font-size:0.9rem;"></i>
|
||
</div>
|
||
<div style="flex:1;min-width:0;">
|
||
<div>
|
||
<a class="nac-link" onclick="navigate('block-detail',{id:'${b.number}'})">#${formatNum(b.number)}</a>
|
||
<span class="badge-nac ${b.isHeartbeat ? 'badge-info' : 'badge-success'} ms-1">${b.isHeartbeat ? '心跳块' : 'TX块'}</span>
|
||
</div>
|
||
<div style="font-size:0.75rem;color:var(--nac-muted)">${timeAgo(b.timestamp)}</div>
|
||
</div>
|
||
<div style="text-align:right;flex-shrink:0;">
|
||
<div style="font-size:0.82rem;">${b.transactionCount || 0} 笔交易</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted);" class="hash-short">${shortHash(b.hash)}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
} else {
|
||
blocksEl.innerHTML = '<div class="empty-state"><i class="bi bi-grid"></i><div>暂无区块数据</div></div>';
|
||
}
|
||
|
||
// Latest Transactions
|
||
const txsEl = document.getElementById('latestTxsList');
|
||
if (txs && txs.transactions && txs.transactions.length > 0) {
|
||
txsEl.innerHTML = txs.transactions.map(tx => `
|
||
<div style="display:flex;align-items:center;padding:0.7rem 1rem;border-bottom:1px solid rgba(26,48,80,0.5);">
|
||
<div style="width:36px;height:36px;background:rgba(255,215,64,0.1);border:1px solid rgba(255,215,64,0.2);border-radius:8px;display:flex;align-items:center;justify-content:center;margin-right:0.8rem;flex-shrink:0;">
|
||
<i class="bi bi-arrow-left-right" style="color:var(--nac-yellow);font-size:0.9rem;"></i>
|
||
</div>
|
||
<div style="flex:1;min-width:0;">
|
||
<div class="hash-short">
|
||
${tx.hash ? `<a class="nac-link" onclick="navigate('tx-detail',{id:'${tx.hash}'})">${shortHash(tx.hash)}</a>` : '<span style="color:var(--nac-muted)">系统交易</span>'}
|
||
</div>
|
||
<div style="font-size:0.75rem;color:var(--nac-muted)">
|
||
<a class="nac-link" onclick="navigate('address-detail',{id:'${tx.from}'})">${shortAddr(tx.from)}</a>
|
||
→ <a class="nac-link" onclick="navigate('address-detail',{id:'${tx.to}'})">${shortAddr(tx.to)}</a>
|
||
</div>
|
||
</div>
|
||
<div style="text-align:right;flex-shrink:0;">
|
||
<div style="font-size:0.82rem;color:var(--nac-green)">${tx.value || '0'} NAC</div>
|
||
<div style="font-size:0.72rem;color:var(--nac-muted)">区块 #${formatNum(tx.blockNumber || tx.blockHeight)}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
} else {
|
||
txsEl.innerHTML = '<div class="empty-state"><i class="bi bi-arrow-left-right"></i><div>暂无交易数据</div></div>';
|
||
}
|
||
}
|
||
|
||
// ── BLOCKS LIST ───────────────────────────────────────────
|
||
async function loadBlocks(page = 1) {
|
||
blocksPage = page;
|
||
const tbody = document.getElementById('blocksTableBody');
|
||
tbody.innerHTML = '<tr><td colspan="6"><div class="loading-spinner"><div class="spinner"></div>加载中...</div></td></tr>';
|
||
|
||
const data = await api(`/blocks?limit=${PAGE_SIZE}`);
|
||
if (!data || !data.blocks) {
|
||
tbody.innerHTML = '<tr><td colspan="6"><div class="empty-state"><i class="bi bi-grid"></i><div>暂无区块数据</div></div></td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = data.blocks.map(b => `
|
||
<tr>
|
||
<td>
|
||
<a class="nac-link" onclick="navigate('block-detail',{id:'${b.number}'})">#${formatNum(b.number)}</a>
|
||
</td>
|
||
<td class="hash-short">
|
||
<a class="nac-link" onclick="navigate('block-detail',{id:'${b.number}'})">${shortHash(b.hash)}</a>
|
||
<button class="copy-btn" onclick="copyText('${b.hash}')" title="复制"><i class="bi bi-copy"></i></button>
|
||
</td>
|
||
<td style="white-space:nowrap;">${timeAgo(b.timestamp)}</td>
|
||
<td>${b.transactionCount || 0}</td>
|
||
<td><span class="badge-nac ${b.isHeartbeat ? 'badge-info' : 'badge-success'}">${b.isHeartbeat ? '心跳块' : 'TX块'}</span></td>
|
||
<td class="hash-short" style="color:var(--nac-muted)">${b.producer ? shortAddr(b.producer) : 'CBPP节点'}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// ── TRANSACTIONS LIST ─────────────────────────────────────
|
||
async function loadTransactions(page = 1) {
|
||
txsPage = page;
|
||
const tbody = document.getElementById('txsTableBody');
|
||
tbody.innerHTML = '<tr><td colspan="7"><div class="loading-spinner"><div class="spinner"></div>加载中...</div></td></tr>';
|
||
|
||
const data = await api('/transactions/latest?limit=50');
|
||
if (!data || !data.transactions) {
|
||
tbody.innerHTML = '<tr><td colspan="7"><div class="empty-state"><i class="bi bi-arrow-left-right"></i><div>暂无交易数据</div></div></td></tr>';
|
||
return;
|
||
}
|
||
|
||
if (data.transactions.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="7"><div class="empty-state"><i class="bi bi-arrow-left-right"></i><div>暂无交易记录(CBPP心跳块不含交易)</div></div></td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = data.transactions.map(tx => `
|
||
<tr>
|
||
<td class="hash-short">
|
||
${tx.hash ? `<a class="nac-link" onclick="navigate('tx-detail',{id:'${tx.hash}'})">${shortHash(tx.hash)}</a>` : '<span style="color:var(--nac-muted)">系统交易</span>'}
|
||
</td>
|
||
<td><a class="nac-link" onclick="navigate('block-detail',{id:'${tx.blockNumber || tx.blockHeight}'})">#${formatNum(tx.blockNumber || tx.blockHeight)}</a></td>
|
||
<td style="white-space:nowrap;">${timeAgo(tx.blockTimestamp)}</td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${tx.from}'})">${shortAddr(tx.from)}</a></td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${tx.to}'})">${shortAddr(tx.to)}</a></td>
|
||
<td style="color:var(--nac-green)">${tx.value || '0'} NAC</td>
|
||
<td><span class="badge-nac badge-success">${tx.status || 'confirmed'}</span></td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// ── BLOCK DETAIL ──────────────────────────────────────────
|
||
async function loadBlockDetail(id) {
|
||
const el = document.getElementById('blockDetailContent');
|
||
document.getElementById('blockDetailBreadcrumb').textContent = '区块 #' + id;
|
||
el.innerHTML = '<div class="loading-spinner"><div class="spinner"></div>加载中...</div>';
|
||
|
||
const data = await api('/blocks/' + id);
|
||
if (!data) {
|
||
el.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><div>区块不存在或加载失败</div></div>';
|
||
return;
|
||
}
|
||
|
||
const block = data;
|
||
el.innerHTML = `
|
||
<div class="detail-header">
|
||
<div class="detail-title"><i class="bi bi-grid-3x3 me-2" style="color:var(--nac-primary)"></i>区块 #${formatNum(block.number || block.height)}</div>
|
||
<div class="d-flex gap-2 flex-wrap mt-2">
|
||
<span class="badge-nac ${block.isHeartbeat ? 'badge-info' : 'badge-success'}">${block.isHeartbeat ? '心跳块' : 'TX驱动块'}</span>
|
||
<span class="badge-nac badge-purple">CBPP 共识</span>
|
||
${block.confirmations ? `<span class="badge-nac badge-success">${block.confirmations} 确认</span>` : ''}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="nac-card mb-3">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title">区块信息</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">区块高度</div>
|
||
<div class="detail-value">${formatNum(block.number || block.height)}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">区块哈希</div>
|
||
<div class="detail-value hash-full">
|
||
${block.hash || '--'}
|
||
${block.hash ? `<button class="copy-btn ms-1" onclick="copyText('${block.hash}')"><i class="bi bi-copy"></i></button>` : ''}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">父区块哈希</div>
|
||
<div class="detail-value hash-full">
|
||
${block.parentHash ? `<a class="nac-link" onclick="navigate('block-detail',{id:'${(block.number||block.height)-1}'})">${block.parentHash}</a>` : '--'}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">时间戳</div>
|
||
<div class="detail-value">${formatTime(block.timestamp)} (${timeAgo(block.timestamp)})</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">交易数量</div>
|
||
<div class="detail-value">${block.transactionCount || 0}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">区块类型</div>
|
||
<div class="detail-value">${block.blockType || (block.isHeartbeat ? '心跳块' : 'TX驱动块')}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">Epoch</div>
|
||
<div class="detail-value">${block.epoch != null ? block.epoch : '--'}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">Round</div>
|
||
<div class="detail-value">${block.round != null ? block.round : '--'}</div>
|
||
</div>
|
||
${block.producer ? `<div class="detail-row">
|
||
<div class="detail-label">出块节点</div>
|
||
<div class="detail-value"><a class="nac-link" onclick="navigate('address-detail',{id:'${block.producer}'})">${block.producer}</a></div>
|
||
</div>` : ''}
|
||
</div>
|
||
</div>
|
||
|
||
${block.transactions && block.transactions.length > 0 ? `
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-arrow-left-right me-2" style="color:var(--nac-yellow)"></i>区块内交易 (${block.transactions.length})</span>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead>
|
||
<tr><th>交易哈希</th><th>发送方</th><th>接收方</th><th>金额</th><th>状态</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
${block.transactions.map(tx => `
|
||
<tr>
|
||
<td class="hash-short">
|
||
${tx.hash ? `<a class="nac-link" onclick="navigate('tx-detail',{id:'${tx.hash}'})">${shortHash(tx.hash)}</a>` : '<span style="color:var(--nac-muted)">系统交易</span>'}
|
||
</td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${tx.from}'})">${shortAddr(tx.from)}</a></td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${tx.to}'})">${shortAddr(tx.to)}</a></td>
|
||
<td style="color:var(--nac-green)">${tx.value || '0'} NAC</td>
|
||
<td><span class="badge-nac badge-success">${tx.status || 'confirmed'}</span></td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
` : `
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title">区块内交易</span>
|
||
</div>
|
||
<div class="empty-state"><i class="bi bi-arrow-left-right"></i><div>此区块为心跳块,不含用户交易</div><div style="font-size:0.78rem;margin-top:0.5rem;">CBPP 宪法原则:心跳块维持链活性,txs=[] 为正常行为</div></div>
|
||
</div>
|
||
`}
|
||
`;
|
||
}
|
||
|
||
// ── TX DETAIL ─────────────────────────────────────────────
|
||
async function loadTxDetail(hash) {
|
||
const el = document.getElementById('txDetailContent');
|
||
el.innerHTML = '<div class="loading-spinner"><div class="spinner"></div>加载中...</div>';
|
||
|
||
const data = await api('/transactions/' + hash);
|
||
if (!data) {
|
||
el.innerHTML = '<div class="empty-state"><i class="bi bi-exclamation-circle"></i><div>交易不存在或加载失败</div></div>';
|
||
return;
|
||
}
|
||
|
||
const tx = data.transaction || data;
|
||
let txData = null;
|
||
try { txData = JSON.parse(tx.data || '{}'); } catch(e) {}
|
||
|
||
el.innerHTML = `
|
||
<div class="detail-header">
|
||
<div class="detail-title"><i class="bi bi-arrow-left-right me-2" style="color:var(--nac-yellow)"></i>交易详情</div>
|
||
<div class="d-flex gap-2 flex-wrap mt-2">
|
||
<span class="badge-nac badge-success">${tx.status || 'confirmed'}</span>
|
||
${txData && txData.type ? `<span class="badge-nac badge-purple">${txData.type}</span>` : ''}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title">交易信息</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">交易哈希</div>
|
||
<div class="detail-value hash-full">
|
||
${tx.hash || hash}
|
||
<button class="copy-btn ms-1" onclick="copyText('${tx.hash || hash}')"><i class="bi bi-copy"></i></button>
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">状态</div>
|
||
<div class="detail-value"><span class="badge-nac badge-success">${tx.status || 'confirmed'}</span></div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">区块</div>
|
||
<div class="detail-value">
|
||
<a class="nac-link" onclick="navigate('block-detail',{id:'${tx.blockNumber || tx.blockHeight}'})">#${formatNum(tx.blockNumber || tx.blockHeight)}</a>
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">时间戳</div>
|
||
<div class="detail-value">${formatTime(tx.blockTimestamp)} (${timeAgo(tx.blockTimestamp)})</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">发送方</div>
|
||
<div class="detail-value hash-full">
|
||
<a class="nac-link" onclick="navigate('address-detail',{id:'${tx.from}'})">${tx.from || '--'}</a>
|
||
${tx.from ? `<button class="copy-btn ms-1" onclick="copyText('${tx.from}')"><i class="bi bi-copy"></i></button>` : ''}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">接收方</div>
|
||
<div class="detail-value hash-full">
|
||
<a class="nac-link" onclick="navigate('address-detail',{id:'${tx.to}'})">${tx.to || '--'}</a>
|
||
${tx.to ? `<button class="copy-btn ms-1" onclick="copyText('${tx.to}')"><i class="bi bi-copy"></i></button>` : ''}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">金额</div>
|
||
<div class="detail-value" style="color:var(--nac-green)">${tx.value || '0'} NAC</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">Gas 费用</div>
|
||
<div class="detail-value">${tx.gas || '0'} (Gas Price: ${tx.gasPrice || '0'})</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">Nonce</div>
|
||
<div class="detail-value">${tx.nonce != null ? tx.nonce : '--'}</div>
|
||
</div>
|
||
${tx.signature ? `<div class="detail-row">
|
||
<div class="detail-label">签名</div>
|
||
<div class="detail-value hash-full" style="font-size:0.75rem">${tx.signature}</div>
|
||
</div>` : ''}
|
||
${tx.data ? `<div class="detail-row">
|
||
<div class="detail-label">输入数据</div>
|
||
<div class="detail-value">
|
||
<pre style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:6px;padding:0.8rem;font-size:0.75rem;overflow-x:auto;margin:0;color:var(--nac-text);">${JSON.stringify(txData || tx.data, null, 2)}</pre>
|
||
</div>
|
||
</div>` : ''}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ── ADDRESS DETAIL ────────────────────────────────────────
|
||
async function loadAddressDetail(address) {
|
||
const el = document.getElementById('addressDetailContent');
|
||
el.innerHTML = '<div class="loading-spinner"><div class="spinner"></div>加载中...</div>';
|
||
|
||
// NAC 地址格式:确保以 0x 开头(API 要求)
|
||
const addrParam = address.startsWith('0x') || address.startsWith('NAC') ? address : '0x' + address;
|
||
|
||
const [addrData, txsData, contractData] = await Promise.all([
|
||
api('/addresses/' + addrParam),
|
||
api('/addresses/' + addrParam + '/transactions?limit=25'),
|
||
api('/contracts/' + addrParam)
|
||
]);
|
||
|
||
const addr = addrData || {};
|
||
const txs = (txsData && txsData.transactions) || [];
|
||
const contract = contractData || {};
|
||
const isContract = addr.isContract || (contract.sourceCode != null);
|
||
|
||
el.innerHTML = `
|
||
<!-- Address Header -->
|
||
<div class="detail-header">
|
||
<div class="d-flex align-items-center gap-3 mb-2">
|
||
${addrIcon(address)}
|
||
<div>
|
||
<div class="detail-title">${isContract ? '智能合约' : '地址'}</div>
|
||
<div class="hash-full" style="font-size:0.82rem;color:var(--nac-muted)">${address}</div>
|
||
</div>
|
||
<button class="copy-btn" onclick="copyText('${address}')" title="复制地址"><i class="bi bi-copy" style="font-size:1rem"></i></button>
|
||
</div>
|
||
<div class="row g-2 mt-1">
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);margin-bottom:0.2rem;">NAC 余额</div>
|
||
<div style="font-size:1.1rem;font-weight:700;color:var(--nac-green)">${addr.balance || '0.000000'} NAC</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);margin-bottom:0.2rem;">交易数量</div>
|
||
<div style="font-size:1.1rem;font-weight:700;">${formatNum(addr.transactionCount || txs.length)}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);margin-bottom:0.2rem;">类型</div>
|
||
<div style="font-size:1.1rem;font-weight:700;">${isContract ? 'Charter 合约' : '普通地址'}</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:8px;padding:0.8rem;">
|
||
<div style="font-size:0.7rem;color:var(--nac-muted);margin-bottom:0.2rem;">代币持有</div>
|
||
<div style="font-size:1.1rem;font-weight:700;">${(addr.tokens || []).length} 种</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="nac-card">
|
||
<div class="nac-tabs" id="addrTabs">
|
||
<button class="nac-tab active" onclick="switchAddrTab('transactions')"><i class="bi bi-arrow-left-right me-1"></i>交易记录</button>
|
||
<button class="nac-tab" onclick="switchAddrTab('tokens')"><i class="bi bi-coin me-1"></i>代币持有</button>
|
||
${isContract ? `<button class="nac-tab" onclick="switchAddrTab('contract')"><i class="bi bi-file-code me-1"></i>合约代码</button>` : ''}
|
||
<button class="nac-tab" onclick="switchAddrTab('info')"><i class="bi bi-info-circle me-1"></i>详细信息</button>
|
||
</div>
|
||
|
||
<!-- Tab: Transactions -->
|
||
<div id="addrTab-transactions" class="tab-content active">
|
||
${txs.length > 0 ? `
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead>
|
||
<tr><th>交易哈希</th><th>区块</th><th>时间</th><th>发送方</th><th>接收方</th><th>金额</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
${txs.map(tx => `
|
||
<tr>
|
||
<td class="hash-short">
|
||
${tx.hash ? `<a class="nac-link" onclick="navigate('tx-detail',{id:'${tx.hash}'})">${shortHash(tx.hash)}</a>` : '<span style="color:var(--nac-muted)">系统交易</span>'}
|
||
</td>
|
||
<td><a class="nac-link" onclick="navigate('block-detail',{id:'${tx.blockNumber}'})">#${formatNum(tx.blockNumber)}</a></td>
|
||
<td style="white-space:nowrap;">${timeAgo(tx.blockTimestamp || tx.timestamp)}</td>
|
||
<td class="hash-short">
|
||
<span class="${tx.from === address ? 'badge-nac badge-warning' : ''}">
|
||
<a class="nac-link" onclick="navigate('address-detail',{id:'${tx.from}'})">${shortAddr(tx.from)}</a>
|
||
</span>
|
||
</td>
|
||
<td class="hash-short">
|
||
<span class="${tx.to === address ? 'badge-nac badge-success' : ''}">
|
||
<a class="nac-link" onclick="navigate('address-detail',{id:'${tx.to}'})">${shortAddr(tx.to)}</a>
|
||
</span>
|
||
</td>
|
||
<td style="color:${tx.from === address ? 'var(--nac-red)' : 'var(--nac-green)'}">${tx.from === address ? '-' : '+'}${tx.value || '0'} NAC</td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
` : `<div class="empty-state"><i class="bi bi-arrow-left-right"></i><div>暂无交易记录</div></div>`}
|
||
</div>
|
||
|
||
<!-- Tab: Tokens -->
|
||
<div id="addrTab-tokens" class="tab-content">
|
||
${addr.tokens && addr.tokens.length > 0 ? `
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead><tr><th>代币</th><th>合约地址</th><th>余额</th></tr></thead>
|
||
<tbody>
|
||
${addr.tokens.map(t => `
|
||
<tr>
|
||
<td>${t.symbol || t.name || 'Unknown'}</td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${t.contract}'})">${shortAddr(t.contract)}</a></td>
|
||
<td>${t.balance || '0'}</td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
` : `<div class="empty-state"><i class="bi bi-coin"></i><div>暂无代币持有记录</div><div style="font-size:0.78rem;margin-top:0.5rem;">ACC-20 代币将在此显示</div></div>`}
|
||
</div>
|
||
|
||
<!-- Tab: Contract (if applicable) -->
|
||
${isContract ? `
|
||
<div id="addrTab-contract" class="tab-content">
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">合约名称</div>
|
||
<div class="detail-value">${contract.name || '未命名合约'}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">编译器</div>
|
||
<div class="detail-value">${contract.compiler || 'charter-1.0.0'}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">合约语言</div>
|
||
<div class="detail-value"><span class="badge-nac badge-purple">${contract.language || 'Charter'}</span></div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">验证状态</div>
|
||
<div class="detail-value">
|
||
${contract.isVerified ? '<span class="badge-nac badge-success">已验证</span>' : '<span class="badge-nac badge-warning">未验证</span>'}
|
||
</div>
|
||
</div>
|
||
${contract.sourceCode ? `
|
||
<div class="detail-row">
|
||
<div class="detail-label">源代码</div>
|
||
<div class="detail-value">
|
||
<pre style="background:var(--nac-card2);border:1px solid var(--nac-border);border-radius:6px;padding:0.8rem;font-size:0.75rem;overflow-x:auto;margin:0;color:var(--nac-text);">${contract.sourceCode}</pre>
|
||
</div>
|
||
</div>` : `
|
||
<div class="detail-row">
|
||
<div class="detail-label">源代码</div>
|
||
<div class="detail-value" style="color:var(--nac-muted)">源代码未公开(Charter 合约验证开发中)</div>
|
||
</div>`}
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
|
||
<!-- Tab: Info -->
|
||
<div id="addrTab-info" class="tab-content">
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">地址</div>
|
||
<div class="detail-value hash-full">
|
||
${address}
|
||
<button class="copy-btn ms-1" onclick="copyText('${address}')"><i class="bi bi-copy"></i></button>
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">余额</div>
|
||
<div class="detail-value" style="color:var(--nac-green)">${addr.balance || '0.000000'} NAC</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">交易数量</div>
|
||
<div class="detail-value">${formatNum(addr.transactionCount || txs.length)}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">地址类型</div>
|
||
<div class="detail-value">${isContract ? 'Charter 智能合约' : '普通地址(NAC 32字节)'}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">最后活动</div>
|
||
<div class="detail-value">${addr.lastActivity ? formatTime(addr.lastActivity) : (txs.length > 0 ? timeAgo(txs[0].blockTimestamp || txs[0].timestamp) : '--')}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">数据来源</div>
|
||
<div class="detail-value" style="color:var(--nac-muted)">${addr.note || 'CBPP-Node-9545'}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function switchAddrTab(tab) {
|
||
document.querySelectorAll('#addrTabs .nac-tab').forEach((btn, i) => {
|
||
const tabs = ['transactions', 'tokens', 'contract', 'info'];
|
||
btn.classList.toggle('active', tabs[i] === tab || btn.textContent.includes(tab));
|
||
});
|
||
document.querySelectorAll('[id^="addrTab-"]').forEach(el => {
|
||
el.classList.toggle('active', el.id === 'addrTab-' + tab);
|
||
});
|
||
}
|
||
|
||
// ── ASSETS ────────────────────────────────────────────────
|
||
async function loadAssets() {
|
||
const el = document.getElementById('assetsContent');
|
||
const data = await api('/assets');
|
||
if (!data || !data.assets || data.assets.length === 0) {
|
||
el.innerHTML = `
|
||
<div class="empty-state">
|
||
<i class="bi bi-gem"></i>
|
||
<div>暂无 RWA 资产数据</div>
|
||
<div style="font-size:0.78rem;margin-top:0.5rem;">${data && data.note ? data.note : 'ACC-20 协议层开发中,资产上链功能即将上线'}</div>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
el.innerHTML = `
|
||
<div class="table-responsive">
|
||
<table class="nac-table">
|
||
<thead>
|
||
<tr><th>资产名称</th><th>类型</th><th>合约地址</th><th>总供应量</th><th>持有者</th><th>状态</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
${data.assets.map(a => `
|
||
<tr>
|
||
<td>${a.name || '--'}</td>
|
||
<td><span class="badge-nac badge-purple">${a.type || 'RWA'}</span></td>
|
||
<td class="hash-short"><a class="nac-link" onclick="navigate('address-detail',{id:'${a.contract}'})">${shortAddr(a.contract)}</a></td>
|
||
<td>${formatNum(a.totalSupply)}</td>
|
||
<td>${formatNum(a.holders)}</td>
|
||
<td><span class="badge-nac badge-success">${a.status || 'active'}</span></td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ── STATS ─────────────────────────────────────────────────
|
||
async function loadStats() {
|
||
const el = document.getElementById('statsContent');
|
||
const data = await api('/network/stats');
|
||
if (!data) {
|
||
el.innerHTML = '<div class="empty-state"><i class="bi bi-bar-chart"></i><div>统计数据加载失败</div></div>';
|
||
return;
|
||
}
|
||
|
||
el.innerHTML = `
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-cpu me-2" style="color:var(--nac-primary)"></i>链上统计</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">当前区块高度</div>
|
||
<div class="detail-value">${formatNum(data.blockHeight)}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">链 ID</div>
|
||
<div class="detail-value">${data.chainId} (${data.chainIdHex})</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">网络</div>
|
||
<div class="detail-value"><span class="badge-nac badge-success">${data.network || 'mainnet'}</span></div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">共识协议</div>
|
||
<div class="detail-value">${data.consensus} - ${data.cbppConsensus}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">出块模式</div>
|
||
<div class="detail-value">${data.blockProductionMode}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">节点序号</div>
|
||
<div class="detail-value">${data.nodeSeq}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">节点出块</div>
|
||
<div class="detail-value">${data.nodeProducerEnabled ? '<span class="badge-nac badge-success">启用</span>' : '<span class="badge-nac badge-warning">禁用</span>'}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-bar-chart me-2" style="color:var(--nac-yellow)"></i>区块分析</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">采样区块数</div>
|
||
<div class="detail-value">${data.sampleBlocks}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">心跳块数量</div>
|
||
<div class="detail-value">${data.heartbeatBlocks} (${((data.heartbeatBlocks/data.sampleBlocks)*100).toFixed(1)}%)</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">TX驱动块数量</div>
|
||
<div class="detail-value">${data.txDrivenBlocks} (${((data.txDrivenBlocks/data.sampleBlocks)*100).toFixed(1)}%)</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">平均每块交易数</div>
|
||
<div class="detail-value">${data.avgTxPerBlock}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">预估总交易数</div>
|
||
<div class="detail-value">${formatNum(data.estimatedTotalTxs)}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">NVM 版本</div>
|
||
<div class="detail-value">v${data.nvmVersion}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">智能合约语言</div>
|
||
<div class="detail-value"><span class="badge-nac badge-purple">${data.smartContractLanguage}</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-shield-check me-2" style="color:var(--nac-green)"></i>CBPP 宪法原则验证</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">宪法原则 4</div>
|
||
<div class="detail-value">
|
||
<span class="badge-nac ${data.cbppInvariant && data.cbppInvariant.nodeSeqEqualsOrLessLatestBlock ? 'badge-success' : 'badge-warning'}">
|
||
${data.cbppInvariant && data.cbppInvariant.nodeSeqEqualsOrLessLatestBlock ? '✓ 通过' : '✗ 异常'}
|
||
</span>
|
||
<div style="font-size:0.75rem;color:var(--nac-muted);margin-top:0.3rem;">${data.cbppInvariant && data.cbppInvariant.description || ''}</div>
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">宪法层</div>
|
||
<div class="detail-value"><span class="badge-nac ${data.constitutionLayer ? 'badge-success' : 'badge-warning'}">${data.constitutionLayer ? '活跃' : '离线'}</span></div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">流体区块模式</div>
|
||
<div class="detail-value"><span class="badge-nac ${data.fluidBlockMode ? 'badge-success' : 'badge-info'}">${data.fluidBlockMode ? '启用' : '禁用'}</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<div class="nac-card">
|
||
<div class="nac-card-header">
|
||
<span class="nac-card-title"><i class="bi bi-diagram-3 me-2" style="color:var(--nac-accent)"></i>网络信息</span>
|
||
</div>
|
||
<div class="p-3">
|
||
<div class="detail-row">
|
||
<div class="detail-label">CSNP 网络</div>
|
||
<div class="detail-value">${data.csnpNetwork || 'single-node'}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">节点数量</div>
|
||
<div class="detail-value">${data.peers || 0}</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">资产协议</div>
|
||
<div class="detail-value"><span class="badge-nac badge-info">${data.assetProtocol || 'ACC-20'}</span></div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-label">数据来源</div>
|
||
<div class="detail-value" style="color:var(--nac-muted)">${data.dataSource}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ── 初始化 ────────────────────────────────────────────────
|
||
function init() {
|
||
// 解析 URL hash
|
||
const hash = window.location.hash;
|
||
if (hash && hash.startsWith('#/')) {
|
||
const parts = hash.slice(2).split('/');
|
||
const page = parts[0];
|
||
const id = parts[1];
|
||
if (page) {
|
||
navigate(page, id ? { id } : {});
|
||
return;
|
||
}
|
||
}
|
||
navigate('home');
|
||
|
||
// 自动刷新首页数据(每30秒)
|
||
setInterval(() => {
|
||
if (currentPage === 'home') loadHome();
|
||
}, 30000);
|
||
}
|
||
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|