Vue 與 Web3 的完美邂逅:如何在 Vue 應(yīng)用中調(diào)用智能合約函數(shù)
隨著區(qū)塊鏈技術(shù)的日益成熟,去中心化應(yīng)用(DApps)正逐漸從概念走向現(xiàn)實,Vue.js 憑借其簡潔的語法、高效的性能和強大的生態(tài)系統(tǒng),成為了構(gòu)建前端應(yīng)用的首選框架之一,將 Vue 的前端能力與 Web3 的后端(區(qū)塊鏈)能力相結(jié)合,可以創(chuàng)造出功能強大、用戶體驗出色的 DApps,本文將詳細講解如何在 Vue 項目中,通過 Web3 技術(shù)調(diào)用部署在以太坊(或兼容網(wǎng)絡(luò))上的智能合約函數(shù)。
核心概念與準(zhǔn)備工作
在開始編碼之前,我們需要了解幾個核心概念:
- 智能合約:運行在區(qū)塊鏈上的自動執(zhí)行程序,是 DApps 的后端邏輯,它定義了數(shù)據(jù)結(jié)構(gòu)和業(yè)務(wù)規(guī)則(一個代幣合約的
transfer函數(shù))。 - Web3.js / Ethers.js:這是與以太坊節(jié)點進行交互的 JavaScript 庫,我們可以用它來連接錢包、讀取鏈上數(shù)據(jù)、發(fā)送交易以及調(diào)用合約函數(shù),本文將以目前更推薦、更現(xiàn)代化的 Ethers.js 為例進行講解。
- 以太坊節(jié)點/提供者:區(qū)塊鏈的“入口”,你可以使用自己搭建的節(jié)點,但更常見的是使用第三方服務(wù),如 Infura 或 Alchemy,它們提供了穩(wěn)定可靠的 API 接口。
- 錢包:用戶的數(shù)字身份和資產(chǎn)管理工具,最常見的是 MetaMask,它允許用戶管理私鑰、簽名交易并與 DApp 進行交互。
準(zhǔn)備工作:
- 安裝 Node.js 和 npm/yarn:確保你的開發(fā)環(huán)境已準(zhǔn)備好。
- 安裝 MetaMask 瀏覽器插件:用于測試和與 DApp 交互。
- 獲取 Infura/Alchemy 的 API Key:注冊一個賬戶,創(chuàng)建一個新的項目,獲取你的 HTTP URL。
- 準(zhǔn)備一個測試網(wǎng)賬戶:從 faucet (如 Sepolia 或 Goerli 測試網(wǎng)) 獲取一些測試 ETH,用于支付交易 Gas 費。
創(chuàng)建 Vue 項目并安裝依賴
我們使用 Vue CLI 或 Vite 創(chuàng)建一個新的 Vue 項目。
# 根據(jù)提示完成項目創(chuàng)建 # 進入項目目錄 cd your-vue-project
安裝 Ethers.js,這是我們與區(qū)塊鏈交互的核心庫。
npm install ethers
連接錢包與初始化 Web3
在 DApp 中,第一步是讓用戶連接他們的錢包,我們將在 Vue 組件中實現(xiàn)這個功能。
創(chuàng)建一個 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 {
// 請求用戶授權(quán)
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
account.value = accounts[0];
// 監(jiān)聽賬戶變化
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,我們只能清空本地狀態(tài))
const disconnectWallet = () => {
account.value = null;
};
</script>
代碼解釋:
- 我們使用
window.ethereum對象(由 MetaMask 注入)來與瀏覽器錢包交互。 eth_requestAccounts方法會彈出一個 MetaMask 確認窗口,請求用戶授權(quán)連接。- 連接成功后,我們將用戶地址保存到
ref中。 - 我們還添加了對
accountsChanged事件的監(jiān)聽,以便在用戶切換賬戶時更新我們的 UI。
定義智能合約 ABI 和地址
為了與合約交互,Ethers.js 需要兩樣?xùn)|西:
- ABI (Application Binary Interface):合約的“說明書”,是一個 JSON 數(shù)組,描述了合約的所有函數(shù)、事件和變量的結(jié)構(gòu)。
- 合約地址:你的智能合約部署到區(qū)塊鏈上的具體地址。
如何獲取 ABI 和地址?
- ABI:在編譯你的 Solidity 合約后(例如使用 Hardhat 或 Truffle),編譯器會生成一個
artifact文件,其中就包含 ABI,你可以直接復(fù)制其中的 JSON 部分。 - 地址:部署合約后,你會得到一個唯一的地址。
假設(shè)我們有一個簡單的 Greeter 合約,它有一個 greet() 函數(shù)(讀?。┖鸵粋€ setGreeting(string) 函數(shù)(寫入)。
ABI 示例 (簡化版):
[
{
"inputs": [],
"name": "greet",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "string", "name": "_greeting", "type": "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 中調(diào)用合約函數(shù)
我們將所有部分組合起來,實現(xiàn)調(diào)用合約函數(shù)的功能,我們創(chuàng)建一個新的組件 src/components/ContractInteraction.vue。
<template>
<div>
<h2>與智能合約交互</h2>
<div v-if="!signer">
<p>請先連接錢包。</p>
</div>
<div v-else>
<!-- 1. 讀取函數(shù) (View Function) -->
<h3>當(dāng)前問候語:</h3>
<p>{{ currentGreeting }}</p>
<button @click="getGreeting">獲取問候語</button>
<hr />
<!-- 2. 寫入函數(shù) (Non-View Function) -->
<h3>設(shè)置新的問候語:</h3>
<input v-model="newGreeting" placeholder="輸入新的問候語" />
<button @click="setGreeting" :disabled="isSetting">設(shè)置</button>
<p v-if="isSetting">交易處理中,請稍候...</p>
<p v-if="txHash" style="color: green;">交易已發(fā)送! 查看詳情: <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';
// 響應(yīng)式狀態(tài)
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. 創(chuàng)建一個 provider (只讀連接)
provider.value = new ethers.providers.JsonRpcProvider('https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY');
// 2. 獲取 signer (讀寫連接,需要用戶授權(quán))
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);
});
// 讀取函數(shù)示例
const getGreeting = async () => {
try {
const greeting = await contract.value.greet();
currentGreeting.value = greeting;
} catch (error) {
console.error("讀取失敗:", error);
}
};
// 寫入函數(shù)示例
const setGreeting = async () => {
if (!newGreeting.value) return;