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)備工作

在開始編碼之前,我們需要了解幾個核心概念:

  1. 智能合約:運行在區(qū)塊鏈上的自動執(zhí)行程序,是 DApps 的后端邏輯,它定義了數(shù)據(jù)結(jié)構(gòu)和業(yè)務(wù)規(guī)則(一個代幣合約的 transfer 函數(shù))。
  2. Web3.js / Ethers.js:這是與以太坊節(jié)點進行交互的 JavaScript 庫,我們可以用它來連接錢包、讀取鏈上數(shù)據(jù)、發(fā)送交易以及調(diào)用合約函數(shù),本文將以目前更推薦、更現(xiàn)代化的 Ethers.js 為例進行講解。
  3. 以太坊節(jié)點/提供者:區(qū)塊鏈的“入口”,你可以使用自己搭建的節(jié)點,但更常見的是使用第三方服務(wù),如 InfuraAlchemy,它們提供了穩(wěn)定可靠的 API 接口。
  4. 錢包:用戶的數(shù)字身份和資產(chǎn)管理工具,最常見的是 MetaMask,它允許用戶管理私鑰、簽名交易并與 DApp 進行交互。

準(zhǔn)備工作:

  1. 安裝 Node.js 和 npm/yarn:確保你的開發(fā)環(huán)境已準(zhǔn)備好。
  2. 安裝 MetaMask 瀏覽器插件:用于測試和與 DApp 交互。
  3. 獲取 Infura/Alchemy 的 API Key:注冊一個賬戶,創(chuàng)建一個新的項目,獲取你的 HTTP URL。
  4. 準(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)|西:

  1. ABI (Application Binary Interface):合約的“說明書”,是一個 JSON 數(shù)組,描述了合約的所有函數(shù)、事件和變量的結(jié)構(gòu)。
  2. 合約地址:你的智能合約部署到區(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;