processStats($rawStats); // 预处理区块列表 $blocks = array_map([$this, 'processBlock'], $rawBlocks['blocks'] ?? []); return View::fetch('index/index', [ 'stats' => $stats, 'blocks' => $blocks, ]); } /** * 区块列表页(分页) */ public function blocks() { $page = max(1, (int) request()->param('page', 1)); $limit = 25; $data = ExplorerApi::getBlocks($limit, $page); $blocks = array_map([$this, 'processBlock'], $data['blocks'] ?? []); $total = (int) ($data['total'] ?? 0); return View::fetch('index/blocks', [ 'blocks' => $blocks, 'total' => $total, 'page' => $page, 'limit' => $limit, 'totalPages' => max(1, (int) ceil($total / $limit)), ]); } /** * 区块详情页 */ public function block() { $number = (int) request()->param('n', 0); if ($number <= 0) { return View::fetch('index/error', ['message' => '无效的区块号']); } $raw = ExplorerApi::getBlock($number); if (!$raw) { return View::fetch('index/block', [ 'block' => null, 'blockNum' => $number, 'transactions' => [], 'txCount' => 0, ]); } $block = $this->processBlock($raw); $block['prevNumber'] = max(0, $number - 1); $block['nextNumber'] = $number + 1; // 预处理交易列表 $transactions = []; foreach ($raw['transactions'] ?? [] as $tx) { $transactions[] = [ 'shortHash' => ExplorerApi::shortHash($tx['hash'] ?? '', 10, 8), 'shortFrom' => ExplorerApi::shortHash($tx['from'] ?? '', 8, 6), 'shortTo' => ExplorerApi::shortHash($tx['to'] ?? '', 8, 6), 'value' => $tx['value'] ?? 0, 'type' => $tx['type'] ?? 'transfer', 'status' => $tx['status'] ?? 'success', ]; } return View::fetch('index/block', [ 'block' => $block, 'blockNum' => $number, 'transactions' => $transactions, 'txCount' => count($transactions), ]); } /** * 搜索 */ public function search() { $q = trim((string) request()->param('q', '')); $type = ''; $result = null; if ($q !== '') { $raw = ExplorerApi::search($q); // API 返回格式: {"type":"block","data":{...}} 或 {"data":{"type":"block","block":{...}}} // 兼容两种格式 $type = $raw['type'] ?? ($raw['data']['type'] ?? ''); if ($type === 'block') { // 新格式:data 直接是区块对象 $b = $raw['data'] ?? []; // 兼容旧格式:data.block if (empty($b['number']) && !empty($raw['data']['block'])) { $b = $raw['data']['block']; } if (!empty($b['number'])) { $result = [ 'number' => $b['number'] ?? 0, 'hash' => $b['hash'] ?? 'N/A', 'timeAgo' => ExplorerApi::timeAgo($b['timestamp'] ?? 0), ]; } } elseif ($type === 'transaction') { $tx = $raw['data']['tx'] ?? $raw['data'] ?? []; if (!empty($tx['hash'])) { $result = [ 'hash' => $tx['hash'] ?? 'N/A', 'blockNumber' => $tx['blockNumber'] ?? 0, 'status' => $tx['status'] ?? 'success', ]; } } elseif ($type === 'address') { $addr = $raw['data']['address'] ?? $raw['data'] ?? []; if (!empty($addr['address'])) { $result = [ 'address' => $addr['address'] ?? $q, 'balance' => $addr['balance'] ?? 0, 'txCount' => $addr['txCount'] ?? 0, ]; } } } return View::fetch('index/search', [ 'q' => htmlspecialchars($q), 'type' => $type, 'result' => $result, ]); } /** * 节点状态页 */ public function nodes() { $rawStats = ExplorerApi::getStats(); $health = ExplorerApi::health(); $stats = $this->processStats($rawStats); $healthJson = json_encode($health, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return View::fetch('index/nodes', [ 'stats' => $stats, 'healthJson' => $healthJson, ]); } // ───────────────────────────────────────────── // 私有辅助方法:数据预处理 // ───────────────────────────────────────────── /** * 预处理统计数据,生成模板直接可用的字段 */ private function processStats(array $raw): array { $cbpp = $raw['cbppConsensus'] ?? 'unknown'; $csnp = $raw['csnpNetwork'] ?? 'unknown'; $constitution = (bool) ($raw['constitutionLayer'] ?? false); $fluid = (bool) ($raw['fluidBlockMode'] ?? false); return [ 'currentBlock' => $raw['currentBlock'] ?? 0, 'cbppConsensus' => $cbpp, 'cbppBadge' => $this->statusBadge($cbpp), 'csnpNetwork' => $csnp, 'csnpBadge' => $this->statusBadge($csnp), 'constitutionLayer' => $constitution, 'constitutionText' => $constitution ? '已激活' : '未激活', 'constitutionBadge' => $constitution ? 'bg-success' : 'bg-secondary', 'fluidBlockMode' => $fluid, 'fluidText' => $fluid ? '已启用' : '未启用', 'fluidBadge' => $fluid ? 'bg-success' : 'bg-secondary', 'chainId' => $raw['chainId'] ?? 20260131, 'network' => $raw['network'] ?? 'mainnet', 'nodeCount' => $raw['nodeCount'] ?? 0, 'totalTransactions' => $raw['totalTransactions'] ?? 0, 'tps' => $raw['tps'] ?? 0, 'avgBlockTime' => $raw['avgBlockTime'] ?? '3.0', ]; } /** * 预处理单个区块数据 */ private function processBlock(array $block): array { $constitution = (bool) ($block['constitutionLayer'] ?? false); $isHeartbeat = (bool) ($block['isHeartbeat'] ?? false); $blockType = $block['blockType'] ?? ($isHeartbeat ? 'heartbeat' : 'tx'); $blockTypeLabel = $block['blockTypeLabel'] ?? ($isHeartbeat ? '心跳块' : '交易块'); $blockTypeNote = $block['blockTypeNote'] ?? ($isHeartbeat ? 'CBPP 宪法原则四:无交易时每60秒产生心跳块,证明网络存活' : 'CBPP 交易驱动块:包含真实链上交易'); return [ 'number' => $block['number'] ?? $block['height'] ?? 0, 'hash' => $block['hash'] ?? 'N/A', 'shortHash' => ExplorerApi::shortHash($block['hash'] ?? '', 16, 12), 'parentHash' => $block['parentHash'] ?? $block['parent_hash'] ?? 'N/A', 'timestamp' => $block['timestamp'] ?? 0, 'formatTime' => ExplorerApi::formatTime($block['timestamp'] ?? 0), 'timeAgo' => ExplorerApi::timeAgo($block['timestamp'] ?? 0), 'validator' => $block['validator'] ?? $block['producer'] ?? 'N/A', 'shortValidator' => ExplorerApi::shortHash($block['validator'] ?? $block['producer'] ?? '', 8, 6), 'txCount' => $block['txCount'] ?? $block['tx_count'] ?? 0, 'size' => $block['size'] ?? 0, 'cbppRound' => $block['cbppRound'] ?? $block['cbpp_round'] ?? '—', 'epoch' => $block['epoch'] ?? 0, 'stateRoot' => $block['stateRoot'] ?? $block['state_root'] ?? 'N/A', 'txRoot' => $block['txRoot'] ?? $block['tx_root'] ?? 'N/A', 'constitutionLayer'=> $constitution, 'constitutionText' => $constitution ? '已激活' : '未激活', 'constitutionBadge'=> $constitution ? 'bg-success' : 'bg-secondary', 'isHeartbeat' => $isHeartbeat, 'blockType' => $blockType, 'blockTypeLabel' => $blockTypeLabel, 'blockTypeNote' => $blockTypeNote, 'blockTypeBadge' => $isHeartbeat ? 'warning text-dark' : 'info text-dark', 'confirmations' => $block['confirmations'] ?? 0, 'dataSource' => $block['dataSource'] ?? 'CBPP-Node-9545', 'transactions' => $block['transactions'] ?? [], ]; } /** * 状态徽章 CSS 类 */ private function statusBadge(string $status): string { return match (strtolower($status)) { 'active', 'connected', 'running', 'online' => 'bg-success', 'syncing', 'pending', 'starting' => 'bg-warning text-dark', 'error', 'failed', 'offline', 'stopped' => 'bg-danger', default => 'bg-secondary', }; } }