从传统后台到链上钱包:我在成都用 Vue.js 搞了个区块链项目

生产环境勿扰
2025-12-21 00:26
阅读 503

早上八点,泡好一杯竹叶青,打开 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 是啥?refreactive 有啥区别?<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 返回的 BigNumberTransactionResponse 这些对象,直接丢进 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,测试妹子当场翻白眼:“这能给客户看?”

优化三板斧:

  1. 虚拟滚动:换用 vue-virtual-scroller,只渲染可视区域。
  2. 字段懒加载:详情弹窗按需请求完整交易回执。
  3. 时间格式缓存:避免每次 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”了(手动狗头)。


给传统企业前端的几点建议

  1. 别迷信“全链上”:用户体验 > 去中心化理想。该用中心化 API 加速的,大胆用。
  2. 状态管理要轻:Pinia 足够,别一上来就 Vuex + 模块拆分,小项目反而累赘。
  3. 兼容性别忽视:工厂车间的老电脑可能还在用 Chrome 70,记得 Babel + core-js。
  4. 调试工具用起来:Vue DevTools + MetaMask 的“Network” tab 是救命稻草。

最后,上周五晚上十点,我提交了最后一行代码,合上笔记本,走出公司。春熙路的夜还热闹着,而我的脑子里还在想:要不要用 Rust 写个 WASM 模块,替代 ethers.js 的部分计算?算了,先回家陪猫吧——毕竟,成都的程序员,卷归卷,生活还是要巴适得板。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝