Vue 与 Web3 的完美邂逅:如何在 Vue 应用中调用智能合约函数


随着区块链技术的日益成熟,去中心化应用(DApps)正逐渐从概念走向现实,Vue.js 凭借其简洁的语法、高效的性能和强大的生态系统,成为了构建前端应用的首选框架之一,将 Vue 的前端能力与 Web3 的后端(区块链)能力相结合,可以创造出功能强大、用户体验出色的 DApps,本文将详细讲解如何在 Vue 项目中,通过 Web3 技术调用部署在以太坊(或兼容网络)上的智能合约函数。

核心概念与准备工作

在开始编码之前,我们需要了解几个核心概念:

  1. 智能合约:运行在区块链上的自动执行程序,是 DApps 的后端逻辑,它定义了数据结构和业务规则(一个代币合约的 transfer 函数)。
  2. Web3.js / Ethers.js:这是与以太坊节点进行交互的 JavaScript 库,我们可以用它来连接钱包、读取链上数据、发送交易以及调用合约函数,本文将以目前更推荐、更现代化的 Ethers.js 为例进行讲解。
  3. 以太坊节点/提供者:区块链的“入口”,你可以使用自己搭建的节点,但更常见的是使用第三方服务,如 InfuraAlchemy,它们提供了稳定可靠的 API 接口。
  4. 钱包:用户的数字身份和资产管理工具,最常见的是 MetaMask,它允许用户管理私钥、签名交易并与 DApp 进行交互。

准备工作:

  1. 安装 Node.js 和 npm/yarn:确保你的开发环境已准备好。
  2. 安装 MetaMask 浏览器插件:用于测试和与 DApp 交互。
  3. 获取 Infura/Alchemy 的 API Key:注册一个账户,创建一个新的项目,获取你的 HTTP URL。
  4. 准备一个测试网账户:从 faucet (如 Sepolia 或 Goerli 测试网) 获取一些测试 ETH,用于支付交易 Gas 费。

创建 Vue 项目并安装依赖

我们使用 Vue CLI 或 Vite 创建一个新的 Vue 项目。

# 根据提示完成项目创建
# 进入项目目录
cd your-vue-project

安装 Ethers.js,这是我们与区块链交互的核心库。

npm install ethers

连接钱包与初始化 Web3

在 DApp 中,第一步是让用户连接他们的钱包,我们将在 Vue 组件中实现这个功能。

创建一个 src/components/WalletConnect.vue 组件:

<template>
  <div>
    <button v-if="!account" @click="connectWallet">连接 MetaMask</button>
    <div v-else>
      <p>已连接: {{ account }}</p>
      <button @click="disconnectWallet">断开连接</button>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { ethers } from 'ethers';
const account = ref(null);
// 连接钱包
const connectWallet = async () => {
  if (window.ethereum) {
    try {
      // 请求用户授权
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      account.value = accounts[0];
      // 监听账户变化
      window.ethereum.on('accountsChanged', (accounts) => {
        if (accounts.length > 0) {
          account.value = accounts[0];
        } else {
          account.value = null;
        }
      });
    } catch (error) {
      console.error("用户拒绝了连接请求", error);
    }
  } else {
    alert("请安装 MetaMask!");
  }
};
// 断开连接(MetaMask 没有直接断开 API,我们只能清空本地状态)
const disconnectWallet = () => {
  account.value = null;
};
</script>

代码解释:

  • 我们使用 window.ethereum 对象(由 MetaMask 注入)来与浏览器钱包交互。
  • eth_requestAccounts 方法会弹出一个 MetaMask 确认窗口,请求用户授权连接。
  • 连接成功后,我们将用户地址保存到 ref 中。
  • 我们还添加了对 accountsChanged 事件的监听,以便在用户切换账户时更新我们的 UI。

定义智能合约 ABI 和地址

为了与合约交互,Ethers.js 需要两样东西:

  1. ABI (Application Binary Interface):合约的“说明书”,是一个 JSON 数组,描述了合约的所有函数、事件和变量的结构。
  2. 合约地址:你的智能合约部署到区块链上的具体地址。

如何获取 ABI 和地址?

  • ABI:在编译你的 Solidity 合约后(例如使用 Hardhat 或 Truffle),编译器会生成一个 artifact 文件,其中就包含 ABI,你可以直接复制其中的 JSON 部分。
  • 地址:部署合约后,你会得到一个唯一的地址。

假设我们有一个简单的 Greeter 合约,它有一个 greet() 函数(读取)和一个 setGreeting(string) 函数(写入)。

ABI 示例 (简化版):

[
  {
    "inputs": [],
    "name": "greet",
    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [{ "internalType": "string", "name": "_greeting", "type&q
随机配图
uot;: "string" }], "name": "setGreeting", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ]

在 Vue 项目中,我们通常将 ABI 和地址保存在单独的文件中,src/contract.js

// src/contract.js
export const contractABI = [ /* 这里粘贴你的完整 ABI */ ];
export const contractAddress = "0x...你的合约地址...";

在 Vue 中调用合约函数

我们将所有部分组合起来,实现调用合约函数的功能,我们创建一个新的组件 src/components/ContractInteraction.vue

<template>
  <div>
    <h2>与智能合约交互</h2>
    <div v-if="!signer">
      <p>请先连接钱包。</p>
    </div>
    <div v-else>
      <!-- 1. 读取函数 (View Function) -->
      <h3>当前问候语:</h3>
      <p>{{ currentGreeting }}</p>
      <button @click="getGreeting">获取问候语</button>
      <hr />
      <!-- 2. 写入函数 (Non-View Function) -->
      <h3>设置新的问候语:</h3>
      <input v-model="newGreeting" placeholder="输入新的问候语" />
      <button @click="setGreeting" :disabled="isSetting">设置</button>
      <p v-if="isSetting">交易处理中,请稍候...</p>
      <p v-if="txHash" style="color: green;">交易已发送! 查看详情: <a :href="`https://sepolia.etherscan.io/tx/${txHash}`" target="_blank">{{ txHash }}</a></p>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ethers } from 'ethers';
import { contractABI, contractAddress } from '../contract';
// 响应式状态
const currentGreeting = ref('加载中...');
const newGreeting = ref('');
const signer = ref(null);
const provider = ref(null);
const contract = ref(null);
const isSetting = ref(false);
const txHash = ref(null);
// 初始化 provider 和 contract
onMounted(async () => {
  // 1. 创建一个 provider (只读连接)
  provider.value = new ethers.providers.JsonRpcProvider('https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY');
  // 2. 获取 signer (读写连接,需要用户授权)
  if (window.ethereum) {
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      signer.value = provider.value.getSigner(accounts[0]);
  }
  // 3. 实例化合约
  contract.value = new ethers.Contract(contractAddress, contractABI, signer.value);
});
// 读取函数示例
const getGreeting = async () => {
  try {
    const greeting = await contract.value.greet();
    currentGreeting.value = greeting;
  } catch (error) {
    console.error("读取失败:", error);
  }
};
// 写入函数示例
const setGreeting = async () => {
  if (!newGreeting.value) return;