从传统后台到链上钱包:我在成都用 Vue.js 搞了个区块链项目
早上八点,泡好一杯竹叶青,打开 IDE,键盘还没热乎,钉钉就弹出消息:“张哥,链上数据展示要得不?今天能看吗?”——又是产品经理催命。我揉了揉眼睛,心里默默吐槽:你当区块链浏览器是 Excel 啊,Ctrl+C/V 就完事?
但说归说,活还得干。作为一家传统制造企业里“被迫数字化”的 Java 后端开发,我原本只跟 Spring Boot 和 Oracle 打交道,结果去年公司搞了个“工业溯源+区块链”的 POC 项目,硬生生把我塞进了前端坑。Vue.js?以前只在简历里写过“了解”,现在?呵,webpack 配置改到凌晨三点都是常态。
不过话说回来,成都这地方节奏慢,加班不至于像北上广那样卷成麻花,所以我还能抽空研究点 Rust(别问,问就是觉得内存安全太香了)。但眼下最要紧的,还是这个 Vue + 区块链的项目——一个给工厂零件做全生命周期溯源的 DApp 前端。
起因:老板说“我们要上链”
去年 Q3,公司高层去深圳开了个“数字经济峰会”,回来就拍板:所有关键零部件必须上链,做到“一物一码、全程可溯”。技术栈?后端用 Hyperledger Fabric(毕竟是私有链,合规嘛),前端……没人会 React,那就 Vue 吧,毕竟团队里有个实习生做过电商后台。
于是,我这个 Java 老兵,被推上了 Vue 前线。说实话,一开始真有点懵。v-model 是啥?ref 和 reactive 有啥区别?<script setup> 又是什么新魔法?但 deadline 不等人,双11前必须上线演示版——对,你没看错,制造业也蹭双11热度,为了拉投资。
项目架构:不止是 CRUD
我们这个项目叫 TraceChain,核心功能是:
- 扫描零件二维码,查看链上流转记录
- 管理员上传新批次信息并上链
- 实时监控节点状态和交易确认数
前端技术栈选型如下:
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.4 | 核心框架 |
| Vite | 5.0 | 构建工具(告别 webpack 冗长启动) |
| Pinia | 2.1 | 状态管理(Vuex 太重了) |
| Element Plus | 2.4 | UI 组件库(老板说要“看起来像大厂”) |
| ethers.js | 6.7 | 与以太坊兼容链交互(Fabric 有 REST Gateway,但模拟环境用 ETH 更方便) |
注:虽然主网是 Fabric,但测试阶段我们搭了个 Ganache 本地链 + Truffle 合约,前端用 ethers.js 调 JSON-RPC,省去了写大量后端代理的麻烦。
踩坑实录:那些让我想砸键盘的瞬间
坑 1:响应式数据和 Web3 对象不兼容
ethers.js 返回的 BigNumber、TransactionResponse 这些对象,直接丢进 ref() 里,Vue 的响应式系统会直接罢工。比如:
// ❌ 错误示范:页面不会更新!
const latestTx = ref(null)
async function fetchLatest() {
const tx = await provider.getTransaction(hash)
latestTx.value = tx // tx 是 ethers 的 Transaction 对象
}
解决办法?序列化 + 重新构造:
// ✅ 正确做法
function serializeTx(tx) {
return {
hash: tx.hash,
from: tx.from,
to: tx.to,
value: tx.value.toString(), // BigNumber 转字符串
blockNumber: tx.blockNumber?.toString() || 'pending'
}
}
const latestTx = ref(null)
async function fetchLatest() {
const tx = await provider.getTransaction(hash)
latestTx.value = serializeTx(tx) // 扔进 plain object
}
这事儿我 debug 了整整一下午,最后在 Vue Discord 里看到有人提类似问题才醒悟——Web3 库返回的大多是不可变或非 POJO 对象,Vue 的 proxy 劫持不到变化。
坑 2:MetaMask 注入的 window.ethereum 在 SSR 下报错
项目后期加了服务端渲染(SEO 需求,投资人要看“专业感”),结果 Node 环境下 window 不存在,一跑就崩:
ReferenceError: window is not defined
解决方案很简单:动态导入 + 条件判断。
// composables/useWeb3.js
import { ref, onMounted } from 'vue'
export function useWeb3() {
const provider = ref(null)
const account = ref('')
onMounted(async () => {
if (typeof window !== 'undefined' && window.ethereum) {
// 动态创建 provider
const { ethers } = await import('ethers')
provider.value = new ethers.BrowserProvider(window.ethereum)
const accounts = await provider.value.send('eth_requestAccounts', [])
account.value = accounts[0]
}
})
return { provider, account }
}
这样既支持 CSR 交互,又不会在 SSR 时报错。不过说实话,SSR + Web3 本身就是个矛盾体——链上数据实时性要求高,SSR 缓存反而可能展示过期信息。后来我们干脆只对首页做 SSR,DApp 页面全部 CSR。
坑 3:Element Plus 的表格在大量链上数据下卡成 PPT
零件溯源记录动辄上千条,每条包含 10+ 字段。用 <el-table> 直接渲染,滚动帧率掉到 5fps,测试妹子当场翻白眼:“这能给客户看?”
优化三板斧:
- 虚拟滚动:换用
vue-virtual-scroller,只渲染可视区域。 - 字段懒加载:详情弹窗按需请求完整交易回执。
- 时间格式缓存:避免每次 render 都调
new Date().toLocaleString()。
<template>
<RecycleScroller
class="scroller"
:items="transactions"
:item-size="60"
key-field="hash"
>
<template #default="{ item }">
<div class="tx-item">
<span>{{ shortHash(item.hash) }}</span>
<span>{{ formatDate(item.timestamp) }}</span>
<!-- 其他字段 -->
</div>
</template>
</RecycleScroller>
</template>
<script setup>
// 使用 computed 缓存格式化结果
const formatDate = useMemoized((ts) => new Date(ts).toLocaleString())
</script>
优化后,10k 条数据滚动流畅如德芙。
区块链交互:不是所有“上链”都那么性感
很多人以为区块链前端就是调个合约、发个交易。但现实是:
- Gas 费波动:测试网还好,主网上用户看到 “$12 Gas Fee” 直接关页面。
- 确认延迟:交易发出后,至少等 6 个区块确认才算“安全”。前端得做轮询 + 状态机。
我们搞了个 useTransactionWatcher 组合式函数:
function useTransactionWatcher(hash) {
const status = ref('pending') // pending → confirmed → failed
const confirmations = ref(0)
watchEffect(async () => {
if (!hash) return
const interval = setInterval(async () => {
const receipt = await provider.getTransactionReceipt(hash)
if (receipt) {
confirmations.value = receipt.confirmations
if (receipt.status === 1) {
status.value = 'confirmed'
} else {
status.value = 'failed'
}
clearInterval(interval)
}
}, 3000)
})
return { status, confirmations }
}
配合 UI 上的状态指示器(比如进度条 + 区块图标闪烁),用户就知道“还在跑,别急”。
成果与反思
项目最终在双11前上线演示环境,老板在投资人面前吹得天花乱坠:“全程链上存证,不可篡改!”——其实后端还是把大部分数据存在 MySQL 里,只有哈希值上链(合规要求如此,别骂)。
但前端体验确实提升了:扫码秒出记录、交易状态清晰、错误提示友好(再也不见 Error: user rejected transaction 这种天书)。
更重要的是,我这个 Java 老兵,终于敢在简历上写“精通 Vue3”了(手动狗头)。
给传统企业前端的几点建议
- 别迷信“全链上”:用户体验 > 去中心化理想。该用中心化 API 加速的,大胆用。
- 状态管理要轻:Pinia 足够,别一上来就 Vuex + 模块拆分,小项目反而累赘。
- 兼容性别忽视:工厂车间的老电脑可能还在用 Chrome 70,记得 Babel + core-js。
- 调试工具用起来:Vue DevTools + MetaMask 的“Network” tab 是救命稻草。
最后,上周五晚上十点,我提交了最后一行代码,合上笔记本,走出公司。春熙路的夜还热闹着,而我的脑子里还在想:要不要用 Rust 写个 WASM 模块,替代 ethers.js 的部分计算?算了,先回家陪猫吧——毕竟,成都的程序员,卷归卷,生活还是要巴适得板。

评论 0