NAC_Blockchain/services/nac-explorer-api/frontend/index.html

1790 lines
72 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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

<!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 &nbsp;|&nbsp; 共识CBPP &nbsp;|&nbsp; 合约语言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>