Vue 与 Web3 的完美邂逅:如何在 Vue 应用中调用智能合约函数
随着区块链技术的日益成熟,去中心化应用(DApps)正逐渐从概念走向现实,Vue.js 凭借其简洁的语法、高效的性能和强大的生态系统,成为了构建前端应用的首选框架之一,将 Vue 的前端能力与 Web3 的后端(区块链)能力相结合,可以创造出功能强大、用户体验出色的 DApps,本文将详细讲解如何在 Vue 项目中,通过 Web3 技术调用部署在以太坊(或兼容网络)上的智能合约函数。
核心概念与准备工作
在开始编码之前,我们需要了解几个核心概念:
- 智能合约:运行在区块链上的自动执行程序,是 DApps 的后端逻辑,它定义了数据结构和业务规则(一个代币合约的
transfer函数)。 - Web3.js / Ethers.js:这是与以太坊节点进行交互的 JavaScript 库,我们可以用它来连接钱包、读取链上数据、发送交易以及调用合约函数,本文将以目前更推荐、更现代化的 Ethers.js 为例进行讲解。
- 以太坊节点/提供者:区块链的“入口”,你可以使用自己搭建的节点,但更常见的是使用第三方服务,如 Infura 或 Alchemy,它们提供了稳定可靠的 API 接口。
- 钱包:用户的数字身份和资产管理工具,最常见的是 MetaMask,它允许用户管理私钥、签名交易并与 DApp 进行交互。
准备工作:
- 安装 Node.js 和 npm/yarn:确保你的开发环境已准备好。
- 安装 MetaMask 浏览器插件:用于测试和与 DApp 交互。
- 获取 Infura/Alchemy 的 API Key:注册一个账户,创建一个新的项目,获取你的 HTTP URL。
- 准备一个测试网账户:从 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 需要两样东西:
- ABI (Application Binary Interface):合约的“说明书”,是一个 JSON 数组,描述了合约的所有函数、事件和变量的结构。
- 合约地址:你的智能合约部署到区块链上的具体地址。
如何获取 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;