NAC_Blockchain/nac-quantum-browser/index_v1_backup.html

782 lines
28 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 量子区块浏览器 | NewAssetChain Explorer</title>
<meta name="description" content="NAC NewAssetChain 量子区块浏览器 - 实时查看 NAC 公链区块、交易、地址和智能合约数据">
<!-- 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-primary: #0d6efd;
--nac-dark: #0a0f1e;
--nac-card: #111827;
--nac-border: #1e2d4a;
--nac-accent: #00d4ff;
--nac-green: #00ff88;
--nac-text: #e2e8f0;
--nac-muted: #94a3b8;
}
body {
background: var(--nac-dark);
color: var(--nac-text);
font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
min-height: 100vh;
}
/* ── Navbar ── */
.navbar-nac {
background: linear-gradient(90deg, #0a0f1e 0%, #0d1b2e 100%);
border-bottom: 1px solid var(--nac-border);
padding: 0.75rem 0;
}
.navbar-brand-nac {
font-size: 1.4rem;
font-weight: 700;
color: var(--nac-accent) !important;
letter-spacing: 0.05em;
}
.navbar-brand-nac span { color: #fff; }
/* ── Hero Search ── */
.hero-section {
background: linear-gradient(135deg, #0a0f1e 0%, #0d1b2e 50%, #091428 100%);
border-bottom: 1px solid var(--nac-border);
padding: 2.5rem 0 2rem;
}
.hero-title {
font-size: 1.8rem;
font-weight: 700;
color: #fff;
margin-bottom: 0.5rem;
}
.hero-subtitle {
color: var(--nac-muted);
font-size: 0.95rem;
margin-bottom: 1.5rem;
}
.search-box {
background: #111827;
border: 1px solid var(--nac-border);
border-radius: 12px;
overflow: hidden;
display: flex;
align-items: center;
}
.search-box input {
background: transparent;
border: none;
color: var(--nac-text);
padding: 0.85rem 1.2rem;
font-size: 0.95rem;
flex: 1;
outline: none;
}
.search-box input::placeholder { color: var(--nac-muted); }
.search-box button {
background: var(--nac-primary);
border: none;
color: #fff;
padding: 0.85rem 1.5rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.search-box button:hover { background: #0b5ed7; }
/* ── Stats Cards ── */
.stats-section {
padding: 1.5rem 0;
background: #0d1420;
border-bottom: 1px solid var(--nac-border);
}
.stat-card {
background: var(--nac-card);
border: 1px solid var(--nac-border);
border-radius: 12px;
padding: 1.2rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
height: 100%;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
flex-shrink: 0;
}
.stat-icon.blue { background: rgba(13,110,253,0.15); color: #60a5fa; }
.stat-icon.green { background: rgba(0,255,136,0.1); color: var(--nac-green); }
.stat-icon.cyan { background: rgba(0,212,255,0.1); color: var(--nac-accent); }
.stat-icon.purple { background: rgba(139,92,246,0.15); color: #a78bfa; }
.stat-icon.orange { background: rgba(251,146,60,0.15); color: #fb923c; }
.stat-label {
font-size: 0.78rem;
color: var(--nac-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.2rem;
}
.stat-value {
font-size: 1.4rem;
font-weight: 700;
color: #fff;
line-height: 1;
}
.stat-sub {
font-size: 0.75rem;
color: var(--nac-muted);
margin-top: 0.2rem;
}
/* ── Main Content ── */
.main-section { padding: 2rem 0; }
.section-card {
background: var(--nac-card);
border: 1px solid var(--nac-border);
border-radius: 12px;
overflow: hidden;
}
.section-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--nac-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.section-title {
font-size: 1rem;
font-weight: 600;
color: #fff;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-title i { color: var(--nac-accent); }
.view-all-link {
font-size: 0.82rem;
color: var(--nac-primary);
text-decoration: none;
}
.view-all-link:hover { color: var(--nac-accent); }
/* ── Block List ── */
.block-item {
padding: 0.9rem 1.5rem;
border-bottom: 1px solid rgba(30,45,74,0.5);
display: flex;
align-items: center;
gap: 1rem;
transition: background 0.15s;
}
.block-item:last-child { border-bottom: none; }
.block-item:hover { background: rgba(255,255,255,0.02); }
.block-num-badge {
background: rgba(13,110,253,0.15);
color: #60a5fa;
border-radius: 8px;
padding: 0.4rem 0.7rem;
font-size: 0.85rem;
font-weight: 600;
min-width: 70px;
text-align: center;
flex-shrink: 0;
}
.block-hash {
font-size: 0.82rem;
color: var(--nac-muted);
font-family: 'Courier New', monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
.block-tx-count {
font-size: 0.8rem;
color: var(--nac-green);
flex-shrink: 0;
}
.block-time {
font-size: 0.78rem;
color: var(--nac-muted);
flex-shrink: 0;
margin-left: auto;
}
/* ── TX List ── */
.tx-item {
padding: 0.9rem 1.5rem;
border-bottom: 1px solid rgba(30,45,74,0.5);
transition: background 0.15s;
}
.tx-item:last-child { border-bottom: none; }
.tx-item:hover { background: rgba(255,255,255,0.02); }
.tx-hash {
font-size: 0.82rem;
color: #60a5fa;
font-family: 'Courier New', monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 280px;
text-decoration: none;
}
.tx-hash:hover { color: var(--nac-accent); }
.tx-type-badge {
font-size: 0.72rem;
padding: 0.2rem 0.6rem;
border-radius: 20px;
font-weight: 600;
}
.tx-type-transfer { background: rgba(0,255,136,0.1); color: var(--nac-green); }
.tx-type-contract { background: rgba(139,92,246,0.15); color: #a78bfa; }
.tx-type-node { background: rgba(251,146,60,0.15); color: #fb923c; }
.tx-type-heartbeat { background: rgba(148,163,184,0.1); color: var(--nac-muted); }
.tx-type-other { background: rgba(13,110,253,0.1); color: #60a5fa; }
/* ── Network Info ── */
.network-badge {
display: inline-flex;
align-items: center;
gap: 0.4rem;
background: rgba(0,255,136,0.1);
color: var(--nac-green);
border: 1px solid rgba(0,255,136,0.2);
border-radius: 20px;
padding: 0.25rem 0.8rem;
font-size: 0.8rem;
font-weight: 600;
}
.network-badge::before {
content: '';
width: 7px;
height: 7px;
background: var(--nac-green);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* ── Chain Info Panel ── */
.chain-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
}
.chain-info-item {
padding: 0.9rem 1.5rem;
border-bottom: 1px solid rgba(30,45,74,0.5);
border-right: 1px solid rgba(30,45,74,0.5);
}
.chain-info-item:nth-child(2n) { border-right: none; }
.chain-info-item:nth-last-child(-n+2) { border-bottom: none; }
.chain-info-label {
font-size: 0.75rem;
color: var(--nac-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.3rem;
}
.chain-info-value {
font-size: 0.9rem;
font-weight: 600;
color: #fff;
font-family: 'Courier New', monospace;
}
.chain-info-value.accent { color: var(--nac-accent); }
.chain-info-value.green { color: var(--nac-green); }
/* ── Loading ── */
.loading-spinner {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--nac-muted);
gap: 0.5rem;
}
/* ── Search Result ── */
#searchResult {
display: none;
margin-top: 1rem;
}
.result-card {
background: var(--nac-card);
border: 1px solid var(--nac-border);
border-radius: 12px;
padding: 1.5rem;
}
.result-title {
font-size: 0.8rem;
color: var(--nac-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.result-value {
font-size: 0.9rem;
color: var(--nac-text);
font-family: 'Courier New', monospace;
word-break: break-all;
}
/* ── Footer ── */
.footer-nac {
background: #080d18;
border-top: 1px solid var(--nac-border);
padding: 1.5rem 0;
color: var(--nac-muted);
font-size: 0.82rem;
text-align: center;
}
.footer-nac a { color: var(--nac-accent); text-decoration: none; }
/* ── Responsive ── */
@media (max-width: 768px) {
.hero-title { font-size: 1.4rem; }
.stat-value { font-size: 1.2rem; }
.chain-info-grid { grid-template-columns: 1fr; }
.chain-info-item { border-right: none; }
.chain-info-item:nth-last-child(-n+2) { border-bottom: 1px solid rgba(30,45,74,0.5); }
.chain-info-item:last-child { border-bottom: none; }
}
</style>
</head>
<body>
<!-- ── Navbar ── -->
<nav class="navbar navbar-nac navbar-expand-lg">
<div class="container">
<a class="navbar-brand navbar-brand-nac" href="/">
<i class="bi bi-hexagon-fill me-2" style="color:var(--nac-accent)"></i>NAC <span>量子浏览器</span>
</a>
<div class="d-flex align-items-center gap-3">
<span class="network-badge" id="networkBadge">Mainnet</span>
<a href="https://newassetchain.io" target="_blank" class="btn btn-sm btn-outline-secondary" style="border-color:var(--nac-border);color:var(--nac-muted)">
<i class="bi bi-globe me-1"></i>官网
</a>
</div>
</div>
</nav>
<!-- ── Hero Search ── -->
<section class="hero-section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<h1 class="hero-title">NAC NewAssetChain 区块浏览器</h1>
<p class="hero-subtitle">搜索区块、交易哈希、地址或 Charter 智能合约</p>
<div class="search-box">
<i class="bi bi-search ms-3" style="color:var(--nac-muted)"></i>
<input type="text" id="searchInput" placeholder="输入区块高度、交易哈希48字节SHA3-384或钱包地址...">
<button onclick="doSearch()"><i class="bi bi-search me-1"></i>搜索</button>
</div>
<div id="searchResult">
<div class="result-card mt-3" id="searchResultContent"></div>
</div>
</div>
</div>
</div>
</section>
<!-- ── Stats ── -->
<section class="stats-section">
<div class="container">
<div class="row g-3">
<div class="col-6 col-md-4 col-lg-2-4">
<div class="stat-card">
<div class="stat-icon blue"><i class="bi bi-stack"></i></div>
<div>
<div class="stat-label">区块高度</div>
<div class="stat-value" id="statBlockHeight">--</div>
<div class="stat-sub">Block Height</div>
</div>
</div>
</div>
<div class="col-6 col-md-4 col-lg-2-4">
<div class="stat-card">
<div class="stat-icon green"><i class="bi bi-arrow-left-right"></i></div>
<div>
<div class="stat-label">总交易数</div>
<div class="stat-value" id="statTotalTx">--</div>
<div class="stat-sub">Total Transactions</div>
</div>
</div>
</div>
<div class="col-6 col-md-4 col-lg-2-4">
<div class="stat-card">
<div class="stat-icon cyan"><i class="bi bi-cpu"></i></div>
<div>
<div class="stat-label">共识协议</div>
<div class="stat-value" id="statConsensus" style="font-size:1.1rem">CBPP</div>
<div class="stat-sub">Constitutional Block</div>
</div>
</div>
</div>
<div class="col-6 col-md-4 col-lg-2-4">
<div class="stat-card">
<div class="stat-icon purple"><i class="bi bi-code-slash"></i></div>
<div>
<div class="stat-label">智能合约</div>
<div class="stat-value" style="font-size:1.1rem">Charter</div>
<div class="stat-sub">NAC Native Language</div>
</div>
</div>
</div>
<div class="col-6 col-md-4 col-lg-2-4">
<div class="stat-card">
<div class="stat-icon orange"><i class="bi bi-diagram-3"></i></div>
<div>
<div class="stat-label">节点数</div>
<div class="stat-value" id="statPeers">--</div>
<div class="stat-sub">Network Peers</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ── Main Content ── -->
<section class="main-section">
<div class="container">
<div class="row g-4">
<!-- Left: Latest Blocks -->
<div class="col-lg-6">
<div class="section-card">
<div class="section-header">
<h2 class="section-title"><i class="bi bi-stack"></i>最新区块</h2>
<a href="#" class="view-all-link" onclick="loadMoreBlocks(); return false;">查看更多 →</a>
</div>
<div id="blockList">
<div class="loading-spinner">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<span>加载中...</span>
</div>
</div>
</div>
</div>
<!-- Right: Latest Transactions -->
<div class="col-lg-6">
<div class="section-card">
<div class="section-header">
<h2 class="section-title"><i class="bi bi-arrow-left-right"></i>最新交易</h2>
<a href="#" class="view-all-link" onclick="loadMoreTxs(); return false;">查看更多 →</a>
</div>
<div id="txList">
<div class="loading-spinner">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<span>加载中...</span>
</div>
</div>
</div>
</div>
<!-- Bottom: Chain Info -->
<div class="col-12">
<div class="section-card">
<div class="section-header">
<h2 class="section-title"><i class="bi bi-info-circle"></i>NAC 公链信息</h2>
<span class="text-muted" style="font-size:0.8rem" id="lastUpdate">--</span>
</div>
<div class="chain-info-grid" id="chainInfoGrid">
<div class="loading-spinner" style="grid-column:1/-1">
<div class="spinner-border spinner-border-sm text-primary" role="status"></div>
<span>加载中...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ── Footer ── -->
<footer class="footer-nac">
<div class="container">
<p class="mb-1">
<strong style="color:var(--nac-accent)">NAC NewAssetChain</strong> 量子区块浏览器 &nbsp;|&nbsp;
共识CBPP &nbsp;|&nbsp; 虚拟机NVM 2.0 &nbsp;|&nbsp; 合约语言Charter &nbsp;|&nbsp; 资产协议ACC-20
</p>
<p class="mb-0" style="font-size:0.75rem">
<a href="https://newassetchain.io">官方网站</a> &nbsp;·&nbsp;
<a href="https://git.newassetchain.io">代码库</a> &nbsp;·&nbsp;
<a href="https://id.newassetchain.io">注册系统</a>
</p>
</div>
</footer>
<script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js"></script>
<script>
// ── API 配置 ──
const API_BASE = '/api/v1';
let blockPage = 1;
let txPage = 1;
// ── 工具函数 ──
function shortHash(hash, len = 16) {
if (!hash) return '--';
if (hash.startsWith('0x')) hash = hash.slice(2);
return hash.length > len * 2 ? hash.slice(0, len) + '...' + hash.slice(-8) : hash;
}
function timeAgo(ts) {
if (!ts) return '--';
const diff = Math.floor(Date.now() / 1000) - 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 formatNumber(n) {
if (n === undefined || n === null) return '--';
return Number(n).toLocaleString('zh-CN');
}
function getTxType(tx) {
if (!tx) return { label: '未知', cls: 'tx-type-other' };
const data = tx.data || tx.input || '';
if (typeof data === 'string' && data.includes('"type"')) {
try {
const parsed = JSON.parse(data);
if (parsed.type === 'node_registration') return { label: '节点注册', cls: 'tx-type-node' };
if (parsed.type === 'heartbeat') return { label: '心跳', cls: 'tx-type-heartbeat' };
if (parsed.type === 'transfer') return { label: '转账', cls: 'tx-type-transfer' };
if (parsed.type === 'contract') return { label: '合约', cls: 'tx-type-contract' };
return { label: parsed.type || '交易', cls: 'tx-type-other' };
} catch {}
}
if (tx.value && tx.value !== '0') return { label: '转账', cls: 'tx-type-transfer' };
if (tx.to === '0x0000000000000000000000000000000000000000' || !tx.to || tx.to === '0000000000000000000000000000000000000000000000000000000000000000') {
return { label: '系统', cls: 'tx-type-heartbeat' };
}
return { label: '交易', cls: 'tx-type-other' };
}
// ── 加载网络统计 ──
async function loadStats() {
try {
const res = await fetch(API_BASE + '/network/stats');
const json = await res.json();
const d = json.data || json;
document.getElementById('statBlockHeight').textContent = formatNumber(d.blockHeight || d.currentBlock);
document.getElementById('statTotalTx').textContent = formatNumber(d.estimatedTotalTxs || d.totalTxs || 0);
document.getElementById('statConsensus').textContent = d.consensus || 'CBPP';
document.getElementById('statPeers').textContent = formatNumber(d.peers || 0);
document.getElementById('lastUpdate').textContent = '最后更新:' + new Date().toLocaleTimeString('zh-CN');
// Chain Info Grid
const chainData = [
{ label: '链 ID', value: d.chainId || '5132611', cls: 'accent' },
{ label: '链 ID (Hex)', value: d.chainIdHex || '0x4E4143', cls: 'accent' },
{ label: '网络类型', value: d.network || 'mainnet', cls: 'green' },
{ label: '共识协议', value: d.consensus || 'CBPP', cls: 'green' },
{ label: '虚拟机版本', value: d.nvmVersion ? 'NVM ' + d.nvmVersion : 'NVM 2.0', cls: '' },
{ label: '智能合约语言', value: d.smartContractLanguage || 'Charter', cls: '' },
{ label: '资产协议', value: d.assetProtocol || 'ACC-20', cls: '' },
{ label: '区块生产模式', value: d.blockProductionMode || 'transaction-driven', cls: '' },
{ label: '宪法层', value: d.constitutionLayer ? '已激活' : '未激活', cls: d.constitutionLayer ? 'green' : '' },
{ label: '流体区块模式', value: d.fluidBlockMode ? '已启用' : '未启用', cls: d.fluidBlockMode ? 'green' : '' },
{ label: '网络协议', value: 'CSNP', cls: '' },
{ label: 'RPC 协议', value: 'NRPC 4.0', cls: '' },
];
document.getElementById('chainInfoGrid').innerHTML = chainData.map(item => `
<div class="chain-info-item">
<div class="chain-info-label">${item.label}</div>
<div class="chain-info-value ${item.cls}">${item.value}</div>
</div>
`).join('');
} catch (e) {
console.error('Stats load error:', e);
}
}
// ── 加载区块列表 ──
async function loadBlocks(limit = 10) {
try {
const res = await fetch(`${API_BASE}/blocks?limit=${limit}`);
const json = await res.json();
const blocks = (json.data && json.data.blocks) ? json.data.blocks : (Array.isArray(json.data) ? json.data : []);
if (!blocks.length) {
document.getElementById('blockList').innerHTML = '<div class="loading-spinner text-muted">暂无区块数据</div>';
return;
}
document.getElementById('blockList').innerHTML = blocks.map(b => `
<div class="block-item">
<div class="block-num-badge">#${formatNumber(b.number || b.height)}</div>
<div style="flex:1;min-width:0">
<div class="block-hash" title="${b.hash || ''}">${shortHash(b.hash)}</div>
<div style="font-size:0.75rem;color:var(--nac-muted);margin-top:2px">
<i class="bi bi-arrow-left-right me-1"></i>${b.txCount || b.transactionCount || 0} 笔交易
</div>
</div>
<div class="block-time">${timeAgo(b.timestamp)}</div>
</div>
`).join('');
} catch (e) {
document.getElementById('blockList').innerHTML = '<div class="loading-spinner text-danger"><i class="bi bi-exclamation-triangle me-1"></i>加载失败</div>';
}
}
// ── 加载交易列表 ──
async function loadTxs(limit = 10) {
try {
const res = await fetch(`${API_BASE}/transactions?limit=${limit}`);
const json = await res.json();
const txs = (json.data && json.data.transactions) ? json.data.transactions :
(json.data && Array.isArray(json.data)) ? json.data : [];
if (!txs.length) {
// 从区块中提取交易
const bRes = await fetch(`${API_BASE}/blocks?limit=20`);
const bJson = await bRes.json();
const blocks = (bJson.data && bJson.data.blocks) ? bJson.data.blocks : [];
const allTxs = [];
for (const b of blocks) {
if (b.transactions && b.transactions.length > 0) {
for (const tx of b.transactions) {
allTxs.push({ ...tx, blockNumber: b.number || b.height, blockTimestamp: b.timestamp });
}
}
if (allTxs.length >= limit) break;
}
if (!allTxs.length) {
document.getElementById('txList').innerHTML = '<div class="loading-spinner text-muted">暂无交易数据</div>';
return;
}
renderTxList(allTxs);
return;
}
renderTxList(txs);
} catch (e) {
document.getElementById('txList').innerHTML = '<div class="loading-spinner text-danger"><i class="bi bi-exclamation-triangle me-1"></i>加载失败</div>';
}
}
function renderTxList(txs) {
document.getElementById('txList').innerHTML = txs.map(tx => {
const type = getTxType(tx);
const hash = tx.hash || tx.txHash || '';
const from = tx.from || '';
return `
<div class="tx-item">
<div class="d-flex align-items-center gap-2 mb-1">
<a class="tx-hash" href="#" title="${hash}" onclick="searchByHash('${hash}'); return false;">${shortHash(hash, 14) || '(系统交易)'}</a>
<span class="tx-type-badge ${type.cls}">${type.label}</span>
</div>
<div style="font-size:0.75rem;color:var(--nac-muted)">
<span>发送方:${shortHash(from, 10) || '--'}</span>
${tx.blockNumber ? `<span class="ms-2">区块 #${formatNumber(tx.blockNumber)}</span>` : ''}
${tx.blockTimestamp ? `<span class="ms-2">${timeAgo(tx.blockTimestamp)}</span>` : ''}
</div>
</div>
`;
}).join('');
}
// ── 搜索功能 ──
async function doSearch() {
const query = document.getElementById('searchInput').value.trim();
if (!query) return;
searchByHash(query);
}
async function searchByHash(query) {
document.getElementById('searchInput').value = query;
const resultDiv = document.getElementById('searchResult');
const contentDiv = document.getElementById('searchResultContent');
resultDiv.style.display = 'block';
contentDiv.innerHTML = '<div class="loading-spinner"><div class="spinner-border spinner-border-sm text-primary" role="status"></div><span>搜索中...</span></div>';
try {
// 尝试作为区块高度搜索
if (/^\d+$/.test(query)) {
const res = await fetch(`${API_BASE}/blocks/${query}`);
if (res.ok) {
const json = await res.json();
const b = json.data || json;
contentDiv.innerHTML = renderBlockDetail(b);
return;
}
}
// 尝试作为区块哈希搜索
const bRes = await fetch(`${API_BASE}/blocks/${query}`);
if (bRes.ok) {
const json = await bRes.json();
contentDiv.innerHTML = renderBlockDetail(json.data || json);
return;
}
// 尝试作为交易哈希搜索
const tRes = await fetch(`${API_BASE}/transactions/${query}`);
if (tRes.ok) {
const json = await tRes.json();
contentDiv.innerHTML = renderTxDetail(json.data || json);
return;
}
// 未找到
contentDiv.innerHTML = `<div class="text-center py-3" style="color:var(--nac-muted)">
<i class="bi bi-search fs-3 d-block mb-2"></i>
未找到 "<strong style="color:#fff">${query}</strong>" 的相关结果
</div>`;
} catch (e) {
contentDiv.innerHTML = `<div class="text-center py-3 text-danger"><i class="bi bi-exclamation-triangle me-1"></i>搜索出错:${e.message}</div>`;
}
}
function renderBlockDetail(b) {
if (!b || (!b.number && !b.height)) return '<div class="text-muted text-center py-3">无区块数据</div>';
return `
<div class="result-title">区块详情</div>
<div class="row g-3">
<div class="col-md-6"><div class="result-title">区块高度</div><div class="result-value" style="color:#60a5fa">#${formatNumber(b.number || b.height)}</div></div>
<div class="col-md-6"><div class="result-title">时间戳</div><div class="result-value">${b.timestamp ? new Date(b.timestamp * 1000).toLocaleString('zh-CN') : '--'}</div></div>
<div class="col-12"><div class="result-title">区块哈希</div><div class="result-value" style="font-size:0.8rem">${b.hash || '--'}</div></div>
<div class="col-12"><div class="result-title">父区块哈希</div><div class="result-value" style="font-size:0.8rem">${b.parentHash || '--'}</div></div>
<div class="col-md-4"><div class="result-title">交易数</div><div class="result-value" style="color:var(--nac-green)">${b.txCount || b.transactionCount || 0}</div></div>
</div>
`;
}
function renderTxDetail(tx) {
if (!tx) return '<div class="text-muted text-center py-3">无交易数据</div>';
const type = getTxType(tx);
return `
<div class="result-title">交易详情</div>
<div class="row g-3">
<div class="col-12"><div class="result-title">交易哈希</div><div class="result-value" style="font-size:0.8rem">${tx.hash || tx.txHash || '--'}</div></div>
<div class="col-md-6"><div class="result-title">类型</div><div class="result-value"><span class="tx-type-badge ${type.cls}">${type.label}</span></div></div>
<div class="col-md-6"><div class="result-title">区块</div><div class="result-value" style="color:#60a5fa">#${formatNumber(tx.blockNumber)}</div></div>
<div class="col-12"><div class="result-title">发送方</div><div class="result-value" style="font-size:0.8rem">${tx.from || '--'}</div></div>
<div class="col-12"><div class="result-title">接收方</div><div class="result-value" style="font-size:0.8rem">${tx.to || '--'}</div></div>
<div class="col-md-4"><div class="result-title">金额</div><div class="result-value">${tx.value || '0'} NAC</div></div>
</div>
`;
}
// ── 加载更多 ──
function loadMoreBlocks() { loadBlocks(30); }
function loadMoreTxs() { loadTxs(30); }
// ── 搜索框回车 ──
document.getElementById('searchInput').addEventListener('keydown', function(e) {
if (e.key === 'Enter') doSearch();
});
// ── 初始化 ──
async function init() {
await Promise.all([loadStats(), loadBlocks(), loadTxs()]);
// 每 30 秒自动刷新统计
setInterval(loadStats, 30000);
// 每 60 秒刷新区块和交易
setInterval(() => { loadBlocks(); loadTxs(); }, 60000);
}
init();
</script>
</body>
</html>