feat(区块链浏览器): 实现区块详情弹窗和中文界面优化
添加区块详情弹窗功能,支持点击区块查看详细信息 优化导航菜单样式,添加活动状态指示器 将界面文本全面中文化并优化交易类型显示 添加区块列表动画效果提升用户体验
This commit is contained in:
@@ -5,17 +5,49 @@
|
|||||||
<div class="max-w-7xl mx-auto px-4 h-full flex items-center justify-between">
|
<div class="max-w-7xl mx-auto px-4 h-full flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="w-6 h-6 border-2 border-blue-500 rounded animate-spin-slow"></div>
|
<div class="w-6 h-6 border-2 border-blue-500 rounded animate-spin-slow"></div>
|
||||||
<span class="text-xl font-bold text-white">FISCO BCOS Explorer</span>
|
<span class="text-xl font-bold text-white">FISCO BCOS 区块链浏览器</span>
|
||||||
<span class="px-2 py-0.5 rounded bg-blue-500/10 text-blue-400 text-xs border border-blue-500/20">Testnet</span>
|
<span class="px-2 py-0.5 rounded bg-blue-500/10 text-blue-400 text-xs border border-blue-500/20">测试网</span>
|
||||||
</div>
|
</div>
|
||||||
<nav class="flex space-x-6 text-sm items-center">
|
<nav class="flex space-x-6 text-sm items-center h-full">
|
||||||
<router-link to="/" class="text-slate-400 hover:text-white flex items-center px-3 py-1 rounded border border-slate-700 hover:border-slate-500 transition-colors">
|
<router-link to="/" class="text-slate-400 hover:text-white flex items-center px-3 py-1 rounded border border-slate-700 hover:border-slate-500 transition-colors mr-2">
|
||||||
<span class="mr-1">↩</span> Mainnet
|
<span class="mr-1">↩</span> 返回首页
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/blockchain/explorer"
|
||||||
|
class="transition-colors hover:text-white h-full flex items-center px-2 relative group"
|
||||||
|
active-class="text-white font-bold"
|
||||||
|
>
|
||||||
|
概览
|
||||||
|
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-blue-500 scale-x-0 group-hover:scale-x-100 transition-transform origin-center active-indicator"></div>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/blockchain/blocks"
|
||||||
|
class="transition-colors hover:text-white h-full flex items-center px-2 relative group"
|
||||||
|
active-class="text-white font-bold"
|
||||||
|
>
|
||||||
|
区块
|
||||||
|
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-blue-500 scale-x-0 group-hover:scale-x-100 transition-transform origin-center active-indicator"></div>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/blockchain/transactions"
|
||||||
|
class="transition-colors hover:text-white h-full flex items-center px-2 relative group"
|
||||||
|
active-class="text-white font-bold"
|
||||||
|
>
|
||||||
|
交易
|
||||||
|
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-blue-500 scale-x-0 group-hover:scale-x-100 transition-transform origin-center active-indicator"></div>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/blockchain/contracts"
|
||||||
|
class="transition-colors hover:text-white h-full flex items-center px-2 relative group"
|
||||||
|
active-class="text-white font-bold"
|
||||||
|
>
|
||||||
|
合约
|
||||||
|
<div class="absolute bottom-0 left-0 w-full h-0.5 bg-blue-500 scale-x-0 group-hover:scale-x-100 transition-transform origin-center active-indicator"></div>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/blockchain/explorer" class="text-white border-b-2 border-blue-500 pb-5 pt-5">概览</router-link>
|
|
||||||
<a href="#" class="hover:text-white transition-colors">区块</a>
|
|
||||||
<a href="#" class="hover:text-white transition-colors">交易</a>
|
|
||||||
<a href="#" class="hover:text-white transition-colors">合约</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -38,4 +70,8 @@
|
|||||||
from { transform: rotate(0deg); }
|
from { transform: rotate(0deg); }
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.router-link-active .active-indicator {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -68,6 +68,21 @@ const routes = [
|
|||||||
path: 'explorer',
|
path: 'explorer',
|
||||||
name: 'BlockchainExplorer',
|
name: 'BlockchainExplorer',
|
||||||
component: () => import('../views/blockchain/Explorer.vue')
|
component: () => import('../views/blockchain/Explorer.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'blocks',
|
||||||
|
name: 'BlockchainBlocks',
|
||||||
|
component: () => import('../views/blockchain/Blocks.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'transactions',
|
||||||
|
name: 'BlockchainTransactions',
|
||||||
|
component: () => import('../views/blockchain/Transactions.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'contracts',
|
||||||
|
name: 'BlockchainContracts',
|
||||||
|
component: () => import('../views/blockchain/Contracts.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/stores/blockchain.js
Normal file
110
src/stores/blockchain.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
export const useBlockchainStore = defineStore('blockchain', () => {
|
||||||
|
const stats = ref([
|
||||||
|
{ label: '区块高度', value: '8,441', icon: '📦' },
|
||||||
|
{ label: '交易总数', value: '142,506', icon: '🔗' },
|
||||||
|
{ label: '活跃节点', value: '21/21', icon: '🌐' },
|
||||||
|
{ label: '平均出块时间', value: '2.1s', icon: '⚡' }
|
||||||
|
])
|
||||||
|
|
||||||
|
const blocks = ref([
|
||||||
|
{ height: 8435, hash: '0x12e6...5027', txs: 5, time: '14s', details: [] },
|
||||||
|
{ height: 8436, hash: '0xb844...0080', txs: 4, time: '12s', details: [] },
|
||||||
|
{ height: 8437, hash: '0x5532...581a', txs: 2, time: '10s', details: [] },
|
||||||
|
{ height: 8438, hash: '0xc34d...7dbd', txs: 4, time: '8s', details: [] },
|
||||||
|
{ height: 8439, hash: '0xd31b...cc3b', txs: 5, time: '6s', details: [] },
|
||||||
|
{ height: 8440, hash: '0x0d90...9885', txs: 1, time: '4s', details: [] },
|
||||||
|
{ height: 8441, hash: '0xa592...8b0d', txs: 6, time: '2s', details: [] },
|
||||||
|
])
|
||||||
|
|
||||||
|
const transactions = ref([
|
||||||
|
{ hash: '0x7d21...4e1a', type: 'Evidence', from: 'Robot-SN088', to: 'Contract:Main', time: '10s' },
|
||||||
|
{ hash: '0x9b32...1f82', type: 'TokenTransfer', from: 'Agency-Node', to: 'Robot-SN012', time: '15s' },
|
||||||
|
{ hash: '0x1c84...9a23', type: 'Alert', from: 'Robot-SN045', to: 'Admin-Node', time: '20s' },
|
||||||
|
{ hash: '0x4e55...bb12', type: 'Evidence', from: 'Robot-SN102', to: 'Contract:Main', time: '25s' },
|
||||||
|
{ hash: '0x2a91...cc84', type: 'ContractDeploy', from: 'Admin-Node', to: 'Blockchain-Net', time: '30s' },
|
||||||
|
{ hash: '0x8f32...dd91', type: 'TokenTransfer', from: 'Robot-SN099', to: 'Agency-Node', time: '35s' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const generateRandomTxs = (count) => {
|
||||||
|
const types = ['Evidence', 'TokenTransfer', 'Alert', 'ContractDeploy']
|
||||||
|
return Array.from({ length: count }).map(() => ({
|
||||||
|
hash: '0x' + Math.random().toString(16).substring(2, 10) + '...',
|
||||||
|
type: types[Math.floor(Math.random() * types.length)],
|
||||||
|
from: `Robot-SN0${Math.floor(Math.random() * 999)}`,
|
||||||
|
to: Math.random() > 0.5 ? 'Contract:Main' : 'Agency-Node',
|
||||||
|
status: 'Success'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化区块详情
|
||||||
|
blocks.value.forEach(b => {
|
||||||
|
if (!b.details || b.details.length === 0) {
|
||||||
|
b.details = generateRandomTxs(b.txs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let timer = null
|
||||||
|
|
||||||
|
const startSimulation = () => {
|
||||||
|
if (timer) return
|
||||||
|
timer = setInterval(() => {
|
||||||
|
// 1. 生成新区块
|
||||||
|
const currentHeight = parseInt(stats.value[0].value.replace(',', ''))
|
||||||
|
const newHeight = currentHeight + 1
|
||||||
|
const txCountInBlock = Math.floor(Math.random() * 6) + 1
|
||||||
|
|
||||||
|
// 2. 更新统计数据
|
||||||
|
stats.value[0].value = newHeight.toLocaleString()
|
||||||
|
const currentTotalTxs = parseInt(stats.value[1].value.replace(',', ''))
|
||||||
|
stats.value[1].value = (currentTotalTxs + txCountInBlock).toLocaleString()
|
||||||
|
|
||||||
|
// 3. 将新块从右侧推入
|
||||||
|
const newHash = '0x' + Math.random().toString(16).substring(2, 6) + '...' + Math.random().toString(16).substring(2, 6)
|
||||||
|
const newBlock = {
|
||||||
|
height: newHeight,
|
||||||
|
hash: newHash,
|
||||||
|
txs: txCountInBlock,
|
||||||
|
time: '0s',
|
||||||
|
details: generateRandomTxs(txCountInBlock)
|
||||||
|
}
|
||||||
|
blocks.value.push(newBlock)
|
||||||
|
if (blocks.value.length > 20) blocks.value.shift()
|
||||||
|
|
||||||
|
// 4. 同步生成交易记录
|
||||||
|
const newTxs = newBlock.details.map(d => ({
|
||||||
|
...d,
|
||||||
|
time: '0s'
|
||||||
|
}))
|
||||||
|
transactions.value.unshift(...newTxs)
|
||||||
|
if (transactions.value.length > 50) transactions.value.splice(50)
|
||||||
|
|
||||||
|
// 5. 更新存量时间
|
||||||
|
blocks.value.forEach((b) => {
|
||||||
|
const seconds = parseInt(b.time) + 2
|
||||||
|
b.time = `${seconds}s`
|
||||||
|
})
|
||||||
|
transactions.value.forEach((tx) => {
|
||||||
|
const seconds = parseInt(tx.time) + 2
|
||||||
|
tx.time = `${seconds}s`
|
||||||
|
})
|
||||||
|
}, 2100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopSimulation = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stats,
|
||||||
|
blocks,
|
||||||
|
transactions,
|
||||||
|
startSimulation,
|
||||||
|
stopSimulation
|
||||||
|
}
|
||||||
|
})
|
||||||
150
src/views/blockchain/Blocks.vue
Normal file
150
src/views/blockchain/Blocks.vue
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-2xl font-bold text-white flex items-center">
|
||||||
|
<span class="mr-2 text-blue-500">📦</span> 区块列表
|
||||||
|
</h2>
|
||||||
|
<div class="text-xs text-slate-500 bg-slate-800/50 px-3 py-1 rounded-full border border-slate-700">
|
||||||
|
每 2.1s 产生一个新块
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Mini -->
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
<div v-for="stat in stats" :key="stat.label" class="bg-[#1e293b]/30 border border-slate-700/50 p-3 rounded-lg">
|
||||||
|
<div class="text-[10px] text-slate-500 uppercase">{{ stat.label }}</div>
|
||||||
|
<div class="text-sm font-mono text-slate-200">{{ stat.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Blocks Table -->
|
||||||
|
<div class="bg-[#1e293b]/50 border border-slate-700 rounded-xl overflow-hidden">
|
||||||
|
<table class="w-full text-left text-sm text-slate-400">
|
||||||
|
<thead class="bg-slate-800/50 text-xs uppercase text-slate-500">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-4">高度</th>
|
||||||
|
<th class="px-6 py-4">区块哈希</th>
|
||||||
|
<th class="px-6 py-4">交易数</th>
|
||||||
|
<th class="px-6 py-4">生成时间</th>
|
||||||
|
<th class="px-6 py-4">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-700/50">
|
||||||
|
<tr v-for="block in sortedBlocks" :key="block.height" class="hover:bg-slate-700/20 transition-colors group">
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<span class="text-blue-400 font-bold">#{{ block.height }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 font-mono text-xs text-slate-400 group-hover:text-slate-200 transition-colors">
|
||||||
|
{{ block.hash }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<span class="px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-400 text-xs border border-blue-500/20">
|
||||||
|
{{ block.txs }} 交易
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-xs text-slate-500 italic">
|
||||||
|
{{ block.time }} 前
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<button @click="openBlockDetails(block)" class="text-xs text-blue-500 hover:text-blue-400 font-medium">查看详情</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Simulation -->
|
||||||
|
<div class="flex justify-center mt-6">
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 cursor-not-allowed">‹</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-blue-500/50 bg-blue-500/10 flex items-center justify-center text-blue-400 font-bold">1</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">2</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">3</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">›</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reuse Block Details Modal from Explorer -->
|
||||||
|
<div v-if="showDetails" class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" @click="showDetails = false"></div>
|
||||||
|
<div class="relative w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-300">
|
||||||
|
<!-- Modal Content (same as Explorer.vue) -->
|
||||||
|
<div class="p-6 border-b border-slate-800 flex justify-between items-center bg-slate-800/50">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-bold text-white">区块 #{{ selectedBlock.height }} 详情</h3>
|
||||||
|
<p class="text-xs text-slate-400 font-mono mt-1">{{ selectedBlock.hash }}</p>
|
||||||
|
</div>
|
||||||
|
<button @click="showDetails = false" class="text-slate-400 hover:text-white transition-colors p-2 text-2xl">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 max-h-[60vh] overflow-y-auto">
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div class="bg-slate-800/30 p-4 rounded-xl border border-slate-700/50">
|
||||||
|
<span class="text-xs text-slate-500 block mb-1">包含交易数</span>
|
||||||
|
<span class="text-lg font-bold text-blue-400">{{ selectedBlock.txs }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-800/30 p-4 rounded-xl border border-slate-700/50">
|
||||||
|
<span class="text-xs text-slate-500 block mb-1">生成时间</span>
|
||||||
|
<span class="text-lg font-bold text-slate-300">{{ selectedBlock.time }} 前</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4 class="text-sm font-semibold text-slate-300 mb-3 px-1">交易列表</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div v-for="tx in selectedBlock.details" :key="tx.hash" class="flex items-center justify-between p-3 bg-slate-800/50 border border-slate-700/30 rounded-lg">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-8 h-8 rounded bg-blue-500/10 flex items-center justify-center text-blue-400 text-[10px] font-bold">
|
||||||
|
{{ tx.type.charAt(0) }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-[10px] font-mono text-slate-300">{{ tx.hash }}</div>
|
||||||
|
<div class="text-[10px] text-slate-500">{{ tx.from }} → {{ tx.to }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-end">
|
||||||
|
<span class="px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-400 text-[9px] border border-blue-500/20 mb-1">
|
||||||
|
{{ translateType(tx.type) }}
|
||||||
|
</span>
|
||||||
|
<span class="text-[9px] text-green-400 flex items-center">已确认</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 border-t border-slate-800 bg-slate-800/30 flex justify-end">
|
||||||
|
<button @click="showDetails = false" class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm transition-colors">关闭窗口</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useBlockchainStore } from '../../stores/blockchain'
|
||||||
|
|
||||||
|
const store = useBlockchainStore()
|
||||||
|
const stats = computed(() => store.stats)
|
||||||
|
const blocks = computed(() => store.blocks)
|
||||||
|
const sortedBlocks = computed(() => [...blocks.value].reverse())
|
||||||
|
|
||||||
|
const selectedBlock = ref(null)
|
||||||
|
const showDetails = ref(false)
|
||||||
|
|
||||||
|
const openBlockDetails = (block) => {
|
||||||
|
selectedBlock.value = block
|
||||||
|
showDetails.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const translateType = (type) => {
|
||||||
|
const map = {
|
||||||
|
'Evidence': '行为存证',
|
||||||
|
'TokenTransfer': '服务结算',
|
||||||
|
'Alert': '异常告警',
|
||||||
|
'ContractDeploy': '合约部署'
|
||||||
|
}
|
||||||
|
return map[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.startSimulation()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
99
src/views/blockchain/Contracts.vue
Normal file
99
src/views/blockchain/Contracts.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-2xl font-bold text-white flex items-center">
|
||||||
|
<span class="mr-2 text-blue-500">📜</span> 合约管理
|
||||||
|
</h2>
|
||||||
|
<div class="text-xs text-slate-500 bg-slate-800/50 px-3 py-1 rounded-full border border-slate-700">
|
||||||
|
已部署智能合约
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contracts Grid -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div v-for="contract in contracts" :key="contract.address"
|
||||||
|
class="bg-[#1e293b]/50 border border-slate-700 rounded-xl p-6 hover:border-blue-500/50 transition-all group">
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center text-blue-400">
|
||||||
|
<span class="text-xl">📄</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white font-bold">{{ contract.name }}</h3>
|
||||||
|
<p class="text-[10px] text-slate-500 font-mono">{{ contract.address }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="px-2 py-1 rounded bg-green-500/10 text-green-400 text-[10px] border border-green-500/20">运行中</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3 mb-6">
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-slate-500">版本</span>
|
||||||
|
<span class="text-slate-300">{{ contract.version }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-slate-500">部署时间</span>
|
||||||
|
<span class="text-slate-300">{{ contract.deployTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span class="text-slate-500">总调用次数</span>
|
||||||
|
<span class="text-blue-400 font-mono">{{ contract.calls }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button class="flex-1 py-2 rounded-lg bg-slate-800 text-slate-300 text-xs hover:bg-slate-700 transition-colors">源代码</button>
|
||||||
|
<button class="flex-1 py-2 rounded-lg bg-blue-600 text-white text-xs hover:bg-blue-500 transition-colors">控制台</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deployment Simulation -->
|
||||||
|
<div class="bg-blue-600/5 border border-blue-500/20 rounded-xl p-8 flex flex-col items-center justify-center text-center space-y-4">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-400">
|
||||||
|
<span class="text-2xl">+</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 class="text-white font-bold">部署新合约</h4>
|
||||||
|
<p class="text-xs text-slate-500 max-w-xs mt-1">支持 Solidity/WASM 智能合约部署到 FISCO BCOS 测试网</p>
|
||||||
|
</div>
|
||||||
|
<button class="px-8 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm transition-colors">开始部署</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const contracts = ref([
|
||||||
|
{
|
||||||
|
name: 'MainLogicContract',
|
||||||
|
address: '0x8f2a2435...9b1c',
|
||||||
|
version: 'v1.2.0',
|
||||||
|
deployTime: '2025-12-01',
|
||||||
|
calls: '45,892'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RobotIdentityManager',
|
||||||
|
address: '0x3d4e1289...1f9a',
|
||||||
|
version: 'v1.0.5',
|
||||||
|
deployTime: '2025-11-15',
|
||||||
|
calls: '12,406'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'EvidenceStorage',
|
||||||
|
address: '0x7a8b5562...2c3d',
|
||||||
|
version: 'v2.1.0',
|
||||||
|
deployTime: '2025-12-10',
|
||||||
|
calls: '89,120'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SettlementService',
|
||||||
|
address: '0x1c84ee92...9a23',
|
||||||
|
version: 'v1.1.2',
|
||||||
|
deployTime: '2025-12-20',
|
||||||
|
calls: '5,230'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
</script>
|
||||||
@@ -9,21 +9,95 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Latest Blocks (Cube Stream) -->
|
<!-- Latest Blocks (Cube Stream) -->
|
||||||
<div class="relative h-48 bg-[#1e293b]/30 border border-slate-700 rounded-xl overflow-hidden flex items-center px-4 space-x-4">
|
<div class="relative h-48 bg-[#1e293b]/30 border border-slate-700 rounded-xl overflow-hidden flex items-center">
|
||||||
<div class="absolute inset-0 bg-gradient-to-r from-[#0f172a] via-transparent to-[#0f172a] z-10 pointer-events-none"></div>
|
<!-- 左侧淡化遮罩:用于块滑出时的视觉效果 -->
|
||||||
|
<div class="absolute inset-y-0 left-0 w-32 bg-gradient-to-r from-[#0f172a] to-transparent z-20 pointer-events-none"></div>
|
||||||
|
|
||||||
<div v-for="block in blocks" :key="block.height"
|
<!-- 区块容器:左对齐,允许向右延伸 -->
|
||||||
class="min-w-[140px] h-32 bg-gradient-to-br from-blue-900 to-slate-900 border border-blue-500/30 rounded-lg p-3 flex flex-col justify-between transform transition-all hover:scale-105 hover:border-blue-400 cursor-pointer group shadow-lg shadow-blue-900/20">
|
<div class="flex-1 h-full flex items-center px-6 overflow-hidden">
|
||||||
<div class="flex justify-between items-start">
|
<TransitionGroup
|
||||||
<span class="text-xs text-blue-300 font-mono">#{{ block.height }}</span>
|
name="block-list"
|
||||||
<span class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></span>
|
tag="div"
|
||||||
|
class="flex space-x-4 items-center relative"
|
||||||
|
>
|
||||||
|
<div v-for="block in blocks" :key="block.height"
|
||||||
|
@click="openBlockDetails(block)"
|
||||||
|
class="min-w-[140px] h-32 bg-gradient-to-br from-blue-900/80 to-slate-900 border border-blue-500/30 rounded-lg p-3 flex flex-col justify-between transform transition-all hover:scale-105 hover:border-blue-400 cursor-pointer group shadow-lg shadow-blue-900/20">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<span class="text-xs text-blue-300 font-mono">#{{ block.height }}</span>
|
||||||
|
<span class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-mono truncate">
|
||||||
|
{{ block.hash }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-white">
|
||||||
|
{{ block.txs }} 交易
|
||||||
|
<span class="block text-[10px] text-slate-500">{{ block.time }} 前</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Block Details Modal -->
|
||||||
|
<div v-if="showDetails" class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm" @click="showDetails = false"></div>
|
||||||
|
<div class="relative w-full max-w-2xl bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-300">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="p-6 border-b border-slate-800 flex justify-between items-center bg-slate-800/50">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-bold text-white">区块 #{{ selectedBlock.height }} 详情</h3>
|
||||||
|
<p class="text-xs text-slate-400 font-mono mt-1">{{ selectedBlock.hash }}</p>
|
||||||
|
</div>
|
||||||
|
<button @click="showDetails = false" class="text-slate-400 hover:text-white transition-colors p-2">
|
||||||
|
<span class="text-2xl">×</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[10px] text-slate-400 font-mono truncate">
|
|
||||||
{{ block.hash }}
|
<!-- Content -->
|
||||||
|
<div class="p-6 max-h-[60vh] overflow-y-auto">
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div class="bg-slate-800/30 p-4 rounded-xl border border-slate-700/50">
|
||||||
|
<span class="text-xs text-slate-500 block mb-1">包含交易数</span>
|
||||||
|
<span class="text-lg font-bold text-blue-400">{{ selectedBlock.txs }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-800/30 p-4 rounded-xl border border-slate-700/50">
|
||||||
|
<span class="text-xs text-slate-500 block mb-1">生成时间</span>
|
||||||
|
<span class="text-lg font-bold text-slate-300">{{ selectedBlock.time }} 前</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="text-sm font-semibold text-slate-300 mb-3 px-1">交易列表</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div v-for="tx in selectedBlock.details" :key="tx.hash"
|
||||||
|
class="flex items-center justify-between p-3 bg-slate-800/50 border border-slate-700/30 rounded-lg hover:border-blue-500/30 transition-colors">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-8 h-8 rounded bg-blue-500/10 flex items-center justify-center text-blue-400 text-[10px] font-bold">
|
||||||
|
{{ tx.type.charAt(0) }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-[10px] font-mono text-slate-300">{{ tx.hash }}</div>
|
||||||
|
<div class="text-[10px] text-slate-500">{{ tx.from }} → {{ tx.to }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-end">
|
||||||
|
<span class="px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-400 text-[9px] border border-blue-500/20 mb-1">
|
||||||
|
{{ translateType(tx.type) }}
|
||||||
|
</span>
|
||||||
|
<span class="text-[9px] text-green-400 flex items-center">
|
||||||
|
<span class="w-1 h-1 rounded-full bg-green-400 mr-1"></span> 已确认
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-white">
|
|
||||||
{{ block.txs }} txs
|
<!-- Footer -->
|
||||||
<span class="block text-[10px] text-slate-500">{{ block.time }} ago</span>
|
<div class="p-4 border-t border-slate-800 bg-slate-800/30 flex justify-end">
|
||||||
|
<button @click="showDetails = false"
|
||||||
|
class="px-6 py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg text-sm font-medium transition-colors">
|
||||||
|
关闭窗口
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,28 +105,28 @@
|
|||||||
<!-- Transactions Table -->
|
<!-- Transactions Table -->
|
||||||
<div class="bg-[#1e293b]/50 border border-slate-700 rounded-xl overflow-hidden">
|
<div class="bg-[#1e293b]/50 border border-slate-700 rounded-xl overflow-hidden">
|
||||||
<div class="px-6 py-4 border-b border-slate-700 flex justify-between items-center">
|
<div class="px-6 py-4 border-b border-slate-700 flex justify-between items-center">
|
||||||
<h3 class="text-white font-bold">Latest Transactions</h3>
|
<h3 class="text-white font-bold">最新交易</h3>
|
||||||
<button class="text-xs text-blue-400 hover:text-blue-300">View All ></button>
|
<button class="text-xs text-blue-400 hover:text-blue-300">查看全部 ></button>
|
||||||
</div>
|
</div>
|
||||||
<table class="w-full text-left text-sm text-slate-400">
|
<table class="w-full text-left text-sm text-slate-400">
|
||||||
<thead class="bg-slate-800/50 text-xs uppercase">
|
<thead class="bg-slate-800/50 text-xs uppercase">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3">Tx Hash</th>
|
<th class="px-6 py-3">交易哈希</th>
|
||||||
<th class="px-6 py-3">Type</th>
|
<th class="px-6 py-3">类型</th>
|
||||||
<th class="px-6 py-3">From</th>
|
<th class="px-6 py-3">发送方</th>
|
||||||
<th class="px-6 py-3">To</th>
|
<th class="px-6 py-3">接收方</th>
|
||||||
<th class="px-6 py-3">Status</th>
|
<th class="px-6 py-3">状态</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-700">
|
<tbody class="divide-y divide-slate-700">
|
||||||
<tr v-for="tx in txs" :key="tx.hash" class="hover:bg-slate-700/30 transition-colors">
|
<tr v-for="tx in txs" :key="tx.hash" class="hover:bg-slate-700/30 transition-colors">
|
||||||
<td class="px-6 py-4 font-mono text-blue-400">{{ tx.hash }}</td>
|
<td class="px-6 py-4 font-mono text-blue-400">{{ tx.hash }}</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
<span class="px-2 py-1 rounded text-xs border" :class="getTypeClass(tx.type)">{{ tx.type }}</span>
|
<span class="px-2 py-1 rounded text-xs border" :class="getTypeClass(tx.type)">{{ translateType(tx.type) }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.from }}</td>
|
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.from }}</td>
|
||||||
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.to }}</td>
|
<td class="px-6 py-4 font-mono truncate max-w-[100px]">{{ tx.to }}</td>
|
||||||
<td class="px-6 py-4 text-green-400 text-xs">Success</td>
|
<td class="px-6 py-4 text-green-400 text-xs">成功</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -61,33 +135,74 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
|
import { useBlockchainStore } from '../../stores/blockchain'
|
||||||
|
|
||||||
const stats = ref([
|
const store = useBlockchainStore()
|
||||||
{ label: 'Block Height', value: '12,403,921' },
|
const stats = computed(() => store.stats)
|
||||||
{ label: 'Total Txs', value: '89,201,332' },
|
const blocks = computed(() => store.blocks)
|
||||||
{ label: 'Nodes Active', value: '4/4' },
|
const txs = computed(() => store.transactions.slice(0, 6))
|
||||||
{ label: 'Avg Block Time', value: '1.02s' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const blocks = ref([
|
// 区块详情弹窗逻辑
|
||||||
{ height: 12403921, hash: '0x8f2a...9b1c', txs: 12, time: '2s' },
|
const selectedBlock = ref(null)
|
||||||
{ height: 12403920, hash: '0x3d4e...1f9a', txs: 45, time: '3s' },
|
const showDetails = ref(false)
|
||||||
{ height: 12403919, hash: '0x7a8b...2c3d', txs: 8, time: '4s' },
|
|
||||||
{ height: 12403918, hash: '0x1c2d...3e4f', txs: 156, time: '5s' },
|
|
||||||
{ height: 12403917, hash: '0x5g6h...7i8j', txs: 23, time: '6s' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const txs = ref([
|
const openBlockDetails = (block) => {
|
||||||
{ hash: '0x8f2a...9b1c', type: 'Evidence', from: 'Robot-082', to: 'Contract:Evidence' },
|
selectedBlock.value = block
|
||||||
{ hash: '0x3d4e...1f9a', type: 'TokenTransfer', from: 'Family-User', to: 'Agency-Wallet' },
|
showDetails.value = true
|
||||||
{ hash: '0x7a8b...2c3d', type: 'Alert', from: 'Robot-015', to: 'Contract:Alert' },
|
}
|
||||||
{ hash: '0x9c0d...1e2f', type: 'Evidence', from: 'Robot-099', to: 'Contract:Evidence' },
|
|
||||||
])
|
const translateType = (type) => {
|
||||||
|
const map = {
|
||||||
|
'Evidence': '行为存证',
|
||||||
|
'TokenTransfer': '服务结算',
|
||||||
|
'Alert': '异常告警',
|
||||||
|
'ContractDeploy': '合约部署'
|
||||||
|
}
|
||||||
|
return map[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
const getTypeClass = (type) => {
|
const getTypeClass = (type) => {
|
||||||
if (type === 'Evidence') return 'bg-blue-900/30 text-blue-300 border-blue-800'
|
if (type === 'Evidence') return 'bg-blue-900/30 text-blue-300 border-blue-800'
|
||||||
if (type === 'Alert') return 'bg-red-900/30 text-red-300 border-red-800'
|
if (type === 'Alert') return 'bg-red-900/30 text-red-300 border-red-800'
|
||||||
|
if (type === 'ContractDeploy') return 'bg-purple-900/30 text-purple-300 border-purple-800'
|
||||||
return 'bg-green-900/30 text-green-300 border-green-800'
|
return 'bg-green-900/30 text-green-300 border-green-800'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.startSimulation()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
// 保持后台仿真运行,这样切换页面时数据是连续的
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.block-list-enter-active,
|
||||||
|
.block-list-leave-active {
|
||||||
|
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 关键:离开的元素设置为绝对定位,确保后续元素能平滑触发 move 动画实现整体滑动 */
|
||||||
|
.block-list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新块从右侧平滑插入 */
|
||||||
|
.block-list-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(160px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旧块向左滑动并淡出 */
|
||||||
|
.block-list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-160px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 整体滑动核心:所有在位元素的移动过渡 */
|
||||||
|
.block-list-move {
|
||||||
|
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
95
src/views/blockchain/Transactions.vue
Normal file
95
src/views/blockchain/Transactions.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-2xl font-bold text-white flex items-center">
|
||||||
|
<span class="mr-2 text-blue-500">🔗</span> 交易列表
|
||||||
|
</h2>
|
||||||
|
<div class="text-xs text-slate-500 bg-slate-800/50 px-3 py-1 rounded-full border border-slate-700">
|
||||||
|
所有链上活动记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transactions Table -->
|
||||||
|
<div class="bg-[#1e293b]/50 border border-slate-700 rounded-xl overflow-hidden">
|
||||||
|
<table class="w-full text-left text-sm text-slate-400">
|
||||||
|
<thead class="bg-slate-800/50 text-xs uppercase text-slate-500">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-4">交易哈希</th>
|
||||||
|
<th class="px-6 py-4">类型</th>
|
||||||
|
<th class="px-6 py-4">发送方</th>
|
||||||
|
<th class="px-6 py-4">接收方</th>
|
||||||
|
<th class="px-6 py-4">状态</th>
|
||||||
|
<th class="px-6 py-4">时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-700/50">
|
||||||
|
<tr v-for="tx in transactions" :key="tx.hash" class="hover:bg-slate-700/20 transition-colors">
|
||||||
|
<td class="px-6 py-4 font-mono text-blue-400 text-xs">
|
||||||
|
{{ tx.hash }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<span class="px-2 py-1 rounded text-[10px] border" :class="getTypeClass(tx.type)">
|
||||||
|
{{ translateType(tx.type) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 font-mono text-xs truncate max-w-[120px]" :title="tx.from">
|
||||||
|
{{ tx.from }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 font-mono text-xs truncate max-w-[120px]" :title="tx.to">
|
||||||
|
{{ tx.to }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<span class="flex items-center text-green-400 text-[10px]">
|
||||||
|
<span class="w-1 h-1 rounded-full bg-green-400 mr-1.5"></span> 成功
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-xs text-slate-500">
|
||||||
|
{{ tx.time }} 前
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Simulation -->
|
||||||
|
<div class="flex justify-center mt-6">
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 cursor-not-allowed">‹</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-blue-500/50 bg-blue-500/10 flex items-center justify-center text-blue-400 font-bold">1</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">2</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">3</button>
|
||||||
|
<button class="w-8 h-8 rounded border border-slate-700 flex items-center justify-center text-slate-500 hover:border-slate-500">›</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { useBlockchainStore } from '../../stores/blockchain'
|
||||||
|
|
||||||
|
const store = useBlockchainStore()
|
||||||
|
const transactions = computed(() => store.transactions)
|
||||||
|
|
||||||
|
const translateType = (type) => {
|
||||||
|
const map = {
|
||||||
|
'Evidence': '行为存证',
|
||||||
|
'TokenTransfer': '服务结算',
|
||||||
|
'Alert': '异常告警',
|
||||||
|
'ContractDeploy': '合约部署'
|
||||||
|
}
|
||||||
|
return map[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTypeClass = (type) => {
|
||||||
|
if (type === 'Evidence') return 'bg-blue-900/30 text-blue-300 border-blue-800'
|
||||||
|
if (type === 'Alert') return 'bg-red-900/30 text-red-300 border-red-800'
|
||||||
|
if (type === 'ContractDeploy') return 'bg-purple-900/30 text-purple-300 border-purple-800'
|
||||||
|
return 'bg-green-900/30 text-green-300 border-green-800'
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.startSimulation()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user