一、Next.js技术栈
✅ 概念介绍
Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性:
✅ 文件系统路由(Pages / App Router)
✅ 服务端渲染(SSR)、静态生成(SSG)、增量静态生成(ISR)
✅ API 路由(后端接口)
✅ 图片优化(
next/image
)✅ CSS / Sass / Tailwind 等样式方案支持
✅ 支持部署到 Vercel、Docker、任意 Node 环境
✅ 新版 App Router 支持 React Server Components(RSC)
✅ 示例代码与讲解(基于 Next.js 13+ App Router)
📁 项目结构(部分)
/app
/page.tsx # 首页
/about/page.tsx # /about 页面
/api/hello/route.ts # API 路由
/components
/Header.tsx
/public
/styles
/globals.css
next.config.js
tsconfig.json
✅ 1. 页面组件(服务端渲染)
// app/page.tsx
export default function HomePage() {
return <h1>Welcome to Web3 DApp</h1>;
}
访问 http://localhost:3000
即可看到页面。
✅ 2. 动态路由页面
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h2>Post: {params.slug}</h2>;
}
访问 /blog/hello-nextjs
会显示 Post: hello-nextjs
✅ 3. API 路由(可连接 Web3 后端)
// app/api/hello/route.ts
export async function GET() {
return new Response(JSON.stringify({ msg: 'Hello API' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
访问 /api/hello
可返回 JSON。
✅ 4. 组件拆分与复用
// components/Header.tsx
export default function Header() {
return <header className="text-xl">My DApp</header>;
}
在页面中导入:
import Header from '@/components/Header';
export default function HomePage() {
return (
<>
<Header />
<main>Hello</main>
</>
);
}
✅ 5. 链上交互集成(如连接 MetaMask)
'use client';
import { useEffect, useState } from 'react';
export default function WalletConnect() {
const [account, setAccount] = useState('');
useEffect(() => {
if (window.ethereum) {
window.ethereum.request({ method: 'eth_requestAccounts' }).then((acc) => {
setAccount(acc[0]);
});
}
}, []);
return <div>Connected Wallet: {account}</div>;
}
✅ 常见应用场景
场景 | 使用说明 |
---|---|
Web3 DApp 前端 | 搭配 Ethers.js / wagmi 等连接钱包 |
博客/文档系统 | 静态生成或服务端渲染 |
SSR SEO 优化 | 提高搜索引擎收录 |
API 接口 + 前端集成 | 一体化开发体验 |
✅ 总结
特性 | 说明 |
---|---|
兼容性 | 与 React 完全兼容,TypeScript 原生支持 |
开发效率 | 文件即路由,快速构建页面与接口 |
生产能力 | SSR/SSG/ISR、图片优化、代码分割等 |
Web3 友好性 | 支持构建钱包连接、NFT 展示、区块数据查询的前端 |
二、Vue 技术栈
✅ 概念介绍
Vue.js 是一套用于构建用户界面的 渐进式 JavaScript 框架,其核心关注视图层,同时易于集成第三方库或现有项目。Vue 提供响应式数据绑定、组件化开发、指令系统等核心特性。
常见的 Vue 技术栈包括:
层级 | 技术 | 说明 |
---|---|---|
框架核心 | Vue 2 / Vue 3 |
前端 MVVM 框架 |
状态管理 | Vuex / Pinia |
全局状态管理 |
路由系统 | Vue Router |
单页应用(SPA)路由管理 |
组件框架 | Element Plus / Vant |
PC / 移动端 UI 组件库 |
网络请求 | Axios |
数据交互 |
构建工具 | Vite / Webpack |
开发构建工具,Vite 为新一代构建方案 |
类型系统 | TypeScript |
Vue 3 原生支持 |
样式工具 | Sass / Tailwind CSS |
样式增强工具 |
✅ 示例代码与讲解
📁 项目结构(Vue 3 + Vite)
/src
/components
HelloWorld.vue
/views
Home.vue
App.vue
main.ts
router/index.ts
store/index.ts
✅ 1. 创建组件并绑定数据
<!-- HelloWorld.vue -->
<template>
<div>
<h1>{{ title }}</h1>
<button @click="count++">Count: {{ count }}</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const title = 'Welcome to Web3 DApp'
const count = ref(0)
</script>
✅ 2. 使用 Vue Router 创建页面路由
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const routes = [{ path: '/', component: Home }]
export const router = createRouter({
history: createWebHistory(),
routes
})
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).mount('#app')
✅ 3. Vuex or Pinia 状态管理示例(Pinia)
// store/useCounter.ts
import { defineStore } from 'pinia'
export const useCounter = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
<script setup lang="ts">
import { useCounter } from '@/store/useCounter'
const counter = useCounter()
</script>
<template>
<button @click="counter.increment()">Count: {{ counter.count }}</button>
</template>
✅ 4. 发送 API 请求(Axios)
// api/user.ts
import axios from 'axios'
export const getUser = () => {
return axios.get('/api/user')
}
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getUser } from '@/api/user'
const user = ref(null)
onMounted(async () => {
const res = await getUser()
user.value = res.data
})
</script>
✅ Vue 技术栈常用于:
场景 | 技术组合示例 |
---|---|
管理后台系统 | Vue + Element Plus + Vuex/Pinia + Axios |
移动端小程序 | Vue + Vant + Pinia + Vite |
Web3 DApp 前端 | Vue + Ethers.js + Web3Modal + Tailwind CSS |
可视化看板平台 | Vue + Echarts + WebSocket + Sass |
✅ 总结
优点 | 说明 |
---|---|
学习曲线平缓 | 易于入门,渐进式架构 |
生态完善 | 官方路由、状态管理、CLI 工具 |
可组合性强 | 支持组合式 API,利于组织逻辑 |
与 Web3 集成能力强 | 搭配 ethers/web3js 可快速实现钱包连接、链上交互等功能 |
如果你想我给出 Web3 场景中 Vue 技术栈的实战 DApp 示例页面,也可以告诉我。
三、Next.js 和 Vue 的 SSR(服务器端渲染)渲染
✅ 概念介绍(SSR 是什么?)
SSR(Server-Side Rendering)服务器端渲染 是一种将页面在服务器端生成 HTML 的方式,服务器将完整 HTML 页面返回给浏览器。
相比 CSR(客户端渲染):
项目 | 客户端渲染 CSR | 服务端渲染 SSR |
---|---|---|
初次加载速度 | 慢 | 快(首屏加载快) |
SEO(搜索引擎优化) | 差 | 好(有完整 HTML) |
动态交互能力 | 强 | SSR 页面需 hydrate(再激活) |
实现复杂度 | 低 | 稍高,需要服务端运行环境支持 |
🚀 Next.js 的 SSR 渲染
Next.js 默认支持 SSR(基于 React),你可以通过 getServerSideProps
来开启服务器端渲染页面。
✅ 示例代码:SSR 页面
// pages/ssr-page.tsx
import React from 'react'
interface Props {
time: string
}
// getServerSideProps 会在每次请求时运行
export async function getServerSideProps() {
return {
props: {
time: new Date().toISOString() // 服务端生成时间
}
}
}
export default function SSRPage({ time }: Props) {
return (
<div>
<h1>🚀 SSR 渲染页面</h1>
<p>服务器生成时间:{time}</p>
</div>
)
}
🔍 注释说明:
getServerSideProps
:每次请求此页面时都会执行,返回的props
会传给页面组件。页面在服务端生成 HTML,然后发送给浏览器,因此服务端返回的时间是准确的。
SEO 友好,因为浏览器拿到的是完整 HTML 内容。
🌿 Vue + Nuxt 的 SSR 渲染
Vue 自身不是 SSR 框架,但 Nuxt.js 是官方推荐的 SSR 框架(对 Vue 的增强封装)。
✅ 示例代码:Nuxt 页面 SSR 渲染
<!-- pages/ssr.vue -->
<template>
<div>
<h1>🚀 SSR 页面</h1>
<p>服务器生成时间:{{ time }}</p>
</div>
</template>
<script setup>
defineProps(['time'])
</script>
<script>
// 运行在服务端,每次请求都会执行
export async function asyncData() {
return {
time: new Date().toISOString()
}
}
</script>
🔍 注释说明:
asyncData
:Nuxt 特有的生命周期函数,只能在服务端运行,用于数据预取。页面请求到达时,服务器会调用
asyncData
,填充数据并生成 HTML。响应发送给客户端时已带数据,无需客户端再请求。
🧩 对比总结:Next.js vs Nuxt.js SSR
对比项 | Next.js | Nuxt.js |
---|---|---|
框架基础 | React | Vue |
数据获取 API | getServerSideProps |
asyncData |
页面文件夹 | pages/ |
pages/ |
默认渲染方式 | SSR 默认 | 可选 SSR/静态 |
类型支持 | TypeScript 完美支持 | Vue 3 中 Composition API 支持 |
📌 使用 SSR 的注意事项
SSR 页面 每次请求都执行服务端代码,对服务器性能有要求。
需考虑 数据获取延迟 与 页面缓存(可结合 Redis 缓存或 ISR)。
页面必须具备 hydrate 机制:HTML 渲染完成后,还要激活前端逻辑(事件绑定、状态管理等)。
如你有 Web3 DApp 页面 SSR 的需求,我可以给你示例如何通过 SSR 预渲染钱包信息、合约状态等内容。是否需要?
四、Next.js 和 Vue 的组件通信
✅ 概念介绍
组件通信 是指组件之间如何传递数据、事件或共享状态。根据组件关系(父子、兄弟、跨层级),通信方式也不同。
场景 | 通信方式(React / Vue) |
---|---|
父子组件 | props(传值) + 事件回调 |
兄弟组件 | 状态提升 / 全局状态管理(Context、Pinia 等) |
跨层级组件 | Context API(React)/ Provide-Inject(Vue) |
1️⃣ Next.js(基于 React)组件通信
示例一:父子组件通信(Props + 回调)
// 父组件 Parent.tsx
import React, { useState } from 'react'
import Child from './Child'
export default function Parent() {
const [message, setMessage] = useState('Hello from parent!')
const handleChildClick = () => {
alert('👦 子组件点击了按钮')
}
return (
<div>
<h2>👨 父组件</h2>
<p>发送给子组件:{message}</p>
<Child msg={message} onChildClick={handleChildClick} />
</div>
)
}
// 子组件 Child.tsx
export default function Child({
msg,
onChildClick
}: {
msg: string
onChildClick: () => void
}) {
return (
<div>
<h3>👶 子组件</h3>
<p>收到来自父组件的信息:{msg}</p>
<button onClick={onChildClick}>告诉父组件我点击了</button>
</div>
)
}
🔍 注释说明
父组件通过
props
向子组件传值。子组件通过
props
中的回调函数,通知父组件事件(如按钮点击)。
示例二:兄弟组件通信(状态提升)
// Page.tsx
import React, { useState } from 'react'
import BrotherA from './BrotherA'
import BrotherB from './BrotherB'
export default function Page() {
const [count, setCount] = useState(0)
return (
<>
<BrotherA count={count} />
<BrotherB setCount={setCount} />
</>
)
}
// BrotherA.tsx
export default function BrotherA({ count }: { count: number }) {
return <p>我是 A 组件,我收到的值是:{count}</p>
}
// BrotherB.tsx
export default function BrotherB({
setCount
}: {
setCount: (n: number) => void
}) {
return (
<button onClick={() => setCount(prev => prev + 1)}>
我是 B 组件,点击我修改 count
</button>
)
}
2️⃣ Vue 组件通信
示例一:父子组件通信(Props + $emit)
<!-- Parent.vue -->
<template>
<div>
<h2>👨 父组件</h2>
<Child :msg="message" @child-click="handleClick" />
</div>
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const message = ref('Hello from parent!')
function handleClick() {
alert('👦 子组件点击了按钮')
}
</script>
<!-- Child.vue -->
<template>
<div>
<h3>👶 子组件</h3>
<p>收到来自父组件的信息:{{ msg }}</p>
<button @click="$emit('child-click')">告诉父组件我点击了</button>
</div>
</template>
<script setup>
defineProps(['msg'])
defineEmits(['child-click'])
</script>
示例二:Provide / Inject(跨层级通信)
<!-- App.vue -->
<template>
<Parent />
</template>
<script setup>
import { provide } from 'vue'
import Parent from './Parent.vue'
provide('theme', 'dark') // 提供 theme 给深层组件
</script>
<!-- DeepChild.vue -->
<template>
<p>🌙 当前主题:{{ theme }}</p>
</template>
<script setup>
import { inject } from 'vue'
const theme = inject('theme') // 跨组件获取
</script>
🔍 总结对比
对比项 | Next.js(React) | Vue 3 |
---|---|---|
父子通信 | props + 回调 | props + $emit |
跨层级 | Context API | provide/inject |
全局状态 | Context, Redux, Zustand 等 | Pinia, Vuex(旧) |
类型支持 | TypeScript 一流 | Composition API 强大 |
响应式系统 | 手动 useState 、useEffect |
自动响应式 ref , watch , computed |
如你需要基于 Web3 钱包连接或合约状态共享的组件通信设计,也可以继续告诉我,我能给出特定案例。
五、Next.js 和 Vue 的接口封装
✅ 概念介绍
接口封装 是指将网络请求(如 API 调用)统一管理,使得组件使用时只需调用方法,而无需关注底层请求细节。
好处包括:
提高复用性(比如统一请求逻辑)
方便维护和扩展
易于切换后端地址、设置 token、错误处理
1️⃣ Next.js 接口封装(基于 Axios)
示例代码:封装 API 请求(/lib/api.ts)
// /lib/api.ts
import axios from 'axios'
const instance = axios.create({
baseURL: '/api', // Next.js 本地 API 或代理地址
timeout: 5000
})
// 请求拦截器
instance.interceptors.request.use(
config => {
// 你可以在这里添加 token
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
},
error => Promise.reject(error)
)
// 响应拦截器
instance.interceptors.response.use(
res => res.data,
err => {
console.error('❌ API 请求出错:', err)
return Promise.reject(err)
}
)
// 封装用户请求
export const getUserInfo = () => instance.get('/user/info')
export const login = (data: { username: string; password: string }) =>
instance.post('/auth/login', data)
示例代码:调用接口(组件中)
'use client'
import { useEffect, useState } from 'react'
import { getUserInfo } from '@/lib/api'
export default function UserProfile() {
const [user, setUser] = useState<any>(null)
useEffect(() => {
getUserInfo().then(data => setUser(data))
}, [])
return (
<div>
<h2>用户信息</h2>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
)
}
🔍 注释说明
lib/api.ts
集中封装所有后端请求。使用
axios.create
创建实例,统一设置 baseURL 和拦截器。使用组件中调用封装方法,避免直接写
axios.get(...)
。
2️⃣ Vue 接口封装(基于 Axios + Composition API)
示例代码:封装 API 请求(/src/utils/http.ts)
// /src/utils/http.ts
import axios from 'axios'
const http = axios.create({
baseURL: '/api',
timeout: 5000
})
// 请求拦截器
http.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// 响应拦截器
http.interceptors.response.use(
res => res.data,
err => {
console.error('❌ 请求出错:', err)
return Promise.reject(err)
}
)
export default http
示例代码:定义 API 模块(/src/api/user.ts)
// /src/api/user.ts
import http from '@/utils/http'
export const getUserInfo = () => http.get('/user/info')
export const login = (data: { username: string; password: string }) =>
http.post('/auth/login', data)
示例代码:Vue 组件中调用(User.vue)
<template>
<div>
<h2>用户信息</h2>
<pre>{{ user }}</pre>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'
const user = ref(null)
onMounted(async () => {
user.value = await getUserInfo()
})
</script>
🔍 对比总结
项目 | Next.js | Vue |
---|---|---|
通用工具目录 | /lib/api.ts |
/src/api/*.ts + /src/utils/http.ts |
常用工具 | Axios、fetch | Axios |
状态管理 | useState / useSWR | ref / reactive |
拦截器 | 支持(用于 token 注入 / 错误处理) | 支持 |
如需拓展支持 Web3 钱包签名、Token 插入、GraphQL 封装、或者与合约交互的 API 包装,请继续告诉我,我可补充更专业的封装案例。
六、Next.js 和 Vue 实现钱包连接(MetaMask)
✅ 概念介绍
在 DApp 中,连接钱包(如 MetaMask)是与区块链交互的第一步。
主要过程如下:
检测是否安装了 MetaMask
请求用户连接账户(授权)
获取当前账户地址
(可选)监听账户变化或网络变化
用于后续签名、交易、合约交互
我们将用 Next.js(React) 和 Vue 3 各写一个示例。
1️⃣ Next.js 连接 MetaMask 示例
📁 技术栈
Next.js 13+(App Router 或 Page Router)
使用
window.ethereum
对象(MetaMask 提供)
示例代码:组件中连接钱包
'use client' // 如果是 App Router
import { useEffect, useState } from 'react'
export default function ConnectWallet() {
const [account, setAccount] = useState<string | null>(null)
// 连接钱包函数
const connectWallet = async () => {
if (typeof window.ethereum === 'undefined') {
alert('请先安装 MetaMask 插件')
return
}
try {
// 请求用户授权连接
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0]) // 设置当前连接账户
} catch (err) {
console.error('用户拒绝连接', err)
}
}
return (
<div className="p-4">
<button onClick={connectWallet} className="px-4 py-2 bg-blue-500 text-white rounded">
连接 MetaMask 钱包
</button>
{account && <p className="mt-2">当前账户: {account}</p>}
</div>
)
}
🔍 注释说明
window.ethereum
是 MetaMask 插件注入的全局对象。使用
eth_requestAccounts
方法让用户授权钱包访问。setAccount
更新状态用于页面展示当前地址。
2️⃣ Vue 3 + Composition API 实现连接 MetaMask
示例代码(ConnectWallet.vue
)
<template>
<div>
<button @click="connectWallet">连接 MetaMask 钱包</button>
<p v-if="account">当前账户:{{ account }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const account = ref('')
// 连接钱包函数
const connectWallet = async () => {
if (typeof window.ethereum === 'undefined') {
alert('请先安装 MetaMask 插件')
return
}
try {
// 请求账户连接
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
})
account.value = accounts[0] // 设置当前连接账户
} catch (error) {
console.error('连接失败:', error)
}
}
</script>
🔍 注释说明
ref
用于定义响应式状态。调用
window.ethereum.request
请求用户授权。授权成功后设置
account
用于显示。
✅ 小贴士
MetaMask 安装检测:用
typeof window.ethereum !== 'undefined'
监听账户变化:
window.ethereum.on('accountsChanged', (accounts) => {
// 账户发生变化,更新状态
})
监听网络变化:
window.ethereum.on('chainChanged', (chainId) => {
// 网络变化后建议 reload 页面
window.location.reload()
})
如果你还希望实现:
钱包断开连接(如 WalletConnect)
Ethers.js 连接 MetaMask 并进行签名或交易
合约调用(如获取余额、调用函数)
可以继续告诉我,我可以逐步扩展讲解。
七、Next.js 和 Vue 实现 NFT 列表展示
✅ 概念介绍
NFT(Non-Fungible Token,非同质化代币)通常基于 ERC-721 或 ERC-1155 标准,代表独特的数字资产。DApp 需要展示用户或某个地址持有的 NFT 列表,常见流程:
连接钱包,获取用户地址
调用区块链或第三方API(如 OpenSea API、Alchemy NFT API、Moralis 等)获取该地址持有的NFT
解析NFT数据,包括 Token ID、名称、图片 URL、描述等
在前端渲染 NFT 列表
下面给出 Next.js 和 Vue 3 简单示例,演示如何调用公共API获取 NFT 并展示。
1️⃣ Next.js 实现 NFT 列表展示示例
技术点:
React + Next.js
使用
fetch
调用第三方API(这里用 Alchemy NFT API 示例)处理异步数据加载
展示图片和文字信息
示例代码
'use client'
import { useEffect, useState } from 'react'
interface NFT {
tokenId: string
title: string
image: string
}
export default function NFTList() {
const [nfts, setNfts] = useState<NFT[]>([])
const [address, setAddress] = useState('0x8ba1f109551bD432803012645Ac136ddd64DBA72') // 示例地址
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!address) return
const fetchNFTs = async () => {
setLoading(true)
try {
// Alchemy NFT API - 替换为自己的API key
const apiKey = 'demo' // 请换成自己的API KEY
const url = `https://eth-mainnet.alchemyapi.io/v2/${apiKey}/getNFTs/?owner=${address}`
const res = await fetch(url)
const data = await res.json()
const nftList = data.ownedNfts.map((item: any) => ({
tokenId: item.id.tokenId,
title: item.title || item.contract.address,
image: item.media[0]?.gateway || '/default-nft.png',
}))
setNfts(nftList)
} catch (error) {
console.error('获取NFT失败:', error)
}
setLoading(false)
}
fetchNFTs()
}, [address])
return (
<div>
<h1 className="text-xl font-bold mb-4">NFT 列表展示</h1>
{loading && <p>加载中...</p>}
{!loading && nfts.length === 0 && <p>未找到NFT</p>}
<ul className="grid grid-cols-3 gap-4">
{nfts.map((nft) => (
<li key={nft.tokenId} className="border p-2 rounded">
<img src={nft.image} alt={nft.title} className="w-full h-48 object-cover mb-2" />
<h3 className="text-sm font-medium">{nft.title}</h3>
<p>Token ID: {parseInt(nft.tokenId, 16)}</p>
</li>
))}
</ul>
</div>
)
}
代码详解
地址(address):NFT 持有者地址,默认示例地址可替换
调用 Alchemy API:根据
owner
查询 NFT 持有信息解析返回数据:提取 NFT tokenId、名称和封面图片 URL
渲染列表:图片+名称+Token ID
loading 状态管理:加载时显示提示
2️⃣ Vue 3 实现 NFT 列表展示示例
技术点:
Vue 3 + Composition API
使用
fetch
异步调用第三方API响应式数据绑定
基础样式布局
示例代码
<template>
<div>
<h1>NFT 列表展示</h1>
<input v-model="address" placeholder="输入钱包地址" class="mb-2 p-1 border" />
<button @click="fetchNFTs" class="mb-4 px-3 py-1 bg-blue-500 text-white rounded">查询NFT</button>
<div v-if="loading">加载中...</div>
<div v-else-if="nfts.length === 0">未找到NFT</div>
<ul class="grid grid-cols-3 gap-4">
<li v-for="nft in nfts" :key="nft.tokenId" class="border p-2 rounded">
<img :src="nft.image" :alt="nft.title" class="w-full h-48 object-cover mb-2" />
<h3 class="text-sm font-medium">{{ nft.title }}</h3>
<p>Token ID: {{ parseInt(nft.tokenId, 16) }}</p>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const address = ref('0x8ba1f109551bD432803012645Ac136ddd64DBA72') // 示例地址
const nfts = ref([])
const loading = ref(false)
const fetchNFTs = async () => {
if (!address.value) {
alert('请输入钱包地址')
return
}
loading.value = true
try {
const apiKey = 'demo' // 替换为你的Alchemy API Key
const url = `https://eth-mainnet.alchemyapi.io/v2/${apiKey}/getNFTs/?owner=${address.value}`
const res = await fetch(url)
const data = await res.json()
nfts.value = data.ownedNfts.map((item) => ({
tokenId: item.id.tokenId,
title: item.title || item.contract.address,
image: item.media[0]?.gateway || '/default-nft.png',
}))
} catch (error) {
console.error('获取NFT失败:', error)
alert('获取NFT失败')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.border {
border: 1px solid #ddd;
border-radius: 6px;
}
.object-cover {
object-fit: cover;
}
</style>
代码详解
响应式数据
address
用于绑定用户输入的钱包地址fetchNFTs
函数调用 Alchemy NFT API,处理数据赋值给nfts
使用
v-for
渲染 NFT 列表,显示图片、标题和 Token ID通过
loading
控制加载状态展示
其他说明
Alchemy API 是以太坊生态常用的 NFT 查询服务,你需要注册获取自己的 API Key
也可以用其他服务如 Moralis、OpenSea API,但可能需要注册和鉴权
Token ID 通常是十六进制,显示时转成十进制更易读
图片可能存在跨域或加载失败,建议设置默认图备用
如果你想要我帮你写完整的合约交互方式(用 Web3.js 或 Ethers.js 从链上直接查询NFT),或者集成钱包连接 + NFT 列表展示,我也可以帮你。
八、Next.js 和 Vue 实现合约事件监听
✅ 概念介绍
智能合约在链上执行时会产生事件(Event),事件是区块链中重要的日志机制,通常用于通知前端某些链上状态变化,比如代币转账、NFT铸造等。
前端监听合约事件,能实现实时响应链上变化,提升用户体验。
合约事件监听的关键点:
通过合约 ABI 和地址,创建合约实例
使用
Web3.js
或Ethers.js
监听特定事件处理事件回调,更新 UI 状态
1️⃣ Next.js 实现合约事件监听示例(使用 Ethers.js)
'use client'
import { useEffect, useState } from 'react'
import { ethers } from 'ethers'
// 示例合约ABI,只包含事件部分
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
]
// 合约地址(示例地址,需替换成真实合约地址)
const contractAddress = "0xYourContractAddressHere"
export default function EventListener() {
const [events, setEvents] = useState<any[]>([])
useEffect(() => {
// 检查浏览器是否支持以太坊钱包(MetaMask等)
if (!window.ethereum) {
alert('请安装 MetaMask 钱包')
return
}
// 创建 Ethers provider 和合约实例
const provider = new ethers.providers.Web3Provider(window.ethereum)
const contract = new ethers.Contract(contractAddress, abi, provider)
// 定义事件处理函数
const onTransfer = (from: string, to: string, value: ethers.BigNumber, event: any) => {
console.log('Transfer事件触发:', { from, to, value: value.toString(), event })
setEvents((prev) => [
...prev,
{ from, to, value: value.toString(), txHash: event.transactionHash }
])
}
// 监听 Transfer 事件
contract.on("Transfer", onTransfer)
// 组件卸载时移除事件监听,防止内存泄漏
return () => {
contract.off("Transfer", onTransfer)
}
}, [])
return (
<div>
<h2>合约事件监听 — Transfer 事件</h2>
<ul>
{events.map((e, i) => (
<li key={i}>
From: {e.from} <br />
To: {e.to} <br />
Value: {e.value} <br />
TxHash: <a href={`https://etherscan.io/tx/${e.txHash}`} target="_blank" rel="noreferrer">{e.txHash}</a>
</li>
))}
</ul>
</div>
)
}
代码详解
ethers.providers.Web3Provider(window.ethereum)
:通过 MetaMask 提供的 provider 连接以太坊new ethers.Contract(...)
:用合约地址和 ABI 创建合约实例contract.on("Transfer", callback)
:监听 Transfer 事件,事件参数和原始事件对象都会传入监听到事件后,将事件数据加入状态数组,页面实时渲染
组件卸载时用
contract.off
移除监听,防止多次绑定导致重复事件
2️⃣ Vue 3 实现合约事件监听示例(使用 Ethers.js)
<template>
<div>
<h2>合约事件监听 — Transfer 事件</h2>
<ul>
<li v-for="(e, index) in events" :key="index">
From: {{ e.from }} <br />
To: {{ e.to }} <br />
Value: {{ e.value }} <br />
TxHash:
<a :href="`https://etherscan.io/tx/${e.txHash}`" target="_blank" rel="noreferrer">
{{ e.txHash }}
</a>
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ethers } from 'ethers'
const events = ref([])
// 示例合约ABI,只包含Transfer事件
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
]
// 合约地址(示例,替换为实际地址)
const contractAddress = '0xYourContractAddressHere'
let contract
const onTransfer = (from, to, value, event) => {
console.log('Transfer事件:', { from, to, value: value.toString(), event })
events.value.push({
from,
to,
value: value.toString(),
txHash: event.transactionHash
})
}
onMounted(async () => {
if (!window.ethereum) {
alert('请安装 MetaMask 钱包')
return
}
const provider = new ethers.providers.Web3Provider(window.ethereum)
contract = new ethers.Contract(contractAddress, abi, provider)
contract.on('Transfer', onTransfer)
})
onUnmounted(() => {
if (contract) {
contract.off('Transfer', onTransfer)
}
})
</script>
代码详解
使用 Vue 3 的生命周期函数
onMounted
、onUnmounted
事件监听同样通过
contract.on
注册,事件触发时更新响应式变量events
组件卸载时通过
contract.off
解除监听,避免内存泄漏
额外说明
事件监听依赖客户端钱包环境(MetaMask)
监听的事件名称必须与合约ABI定义一致
你可以监听多个事件,或者监听特定条件过滤事件
事件回调参数由合约事件定义决定,一般带有
indexed
的参数可作为过滤条件生产环境建议用 WebSocket provider(如 Infura、Alchemy WebSocket)保持稳定连接
监听合约事件适合前端实时交互场景,也可以结合后端服务做事件存储与通知
如果需要,我可以帮你写更复杂的事件监听示例,比如:
使用 WebSocket provider 实现稳定监听
监听多个事件并分类展示
用 React Hook 或 Vue Composition API 抽象复用监听逻辑
九、Next.js 和 Vue 实现链上状态与链下数据结合
✅ 概念介绍
链上状态 指的是存储在区块链智能合约中的数据,比如账户余额、NFT 所有权、交易状态等。这些数据去中心化且不可篡改,但读取速度和成本较高。
链下数据 指的是存储在区块链外的数据库或存储系统中的数据,如用户的个人资料、评论、交易历史等丰富的扩展信息。链下数据访问速度快,存储成本低,但安全性和可信度依赖应用设计。
结合链上状态与链下数据,可以实现完整且高效的 DApp 用户体验。例如:
显示某个 NFT 的链上所有权信息(链上状态)
显示该 NFT 的详细描述、图片、历史交易记录(链下数据)
实现思路
链上数据读取:通过智能合约接口(如
Web3.js
或Ethers.js
)调用读取链上状态。链下数据查询:调用后端 API 或数据库接口,获取链下存储的结构化数据。
结合展示:前端将链上数据与链下数据合并后,渲染完整的页面信息。
1️⃣ Next.js 实现链上状态与链下数据结合示例
'use client'
import { useEffect, useState } from 'react'
import { ethers } from 'ethers'
// 示例合约 ABI,只包含读取 NFT 所有者函数
const abi = [
"function ownerOf(uint256 tokenId) view returns (address)"
]
// 合约地址示例
const contractAddress = "0xYourContractAddressHere"
// 假设链下API接口,返回NFT详细信息
async function fetchOffChainData(tokenId: number) {
const res = await fetch(`/api/nft/${tokenId}`)
if (!res.ok) throw new Error('链下数据获取失败')
return res.json()
}
export default function NFTDetail({ tokenId }: { tokenId: number }) {
const [owner, setOwner] = useState<string | null>(null)
const [offChainData, setOffChainData] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function loadData() {
try {
if (!window.ethereum) {
setError('请安装MetaMask钱包')
return
}
// 1. 链上状态:查询 NFT 所有者
const provider = new ethers.providers.Web3Provider(window.ethereum)
const contract = new ethers.Contract(contractAddress, abi, provider)
const ownerAddress = await contract.ownerOf(tokenId)
setOwner(ownerAddress)
// 2. 链下数据:查询后端详细信息
const offChain = await fetchOffChainData(tokenId)
setOffChainData(offChain)
} catch (err: any) {
setError(err.message)
}
}
loadData()
}, [tokenId])
if (error) return <div>错误:{error}</div>
if (!owner || !offChainData) return <div>加载中...</div>
return (
<div>
<h1>{offChainData.name}</h1>
<img src={offChainData.image} alt={offChainData.name} width={300} />
<p>描述: {offChainData.description}</p>
<p>链上拥有者: {owner}</p>
<p>更多链下信息: {JSON.stringify(offChainData.metadata)}</p>
</div>
)
}
代码详解
使用
ethers.Contract
读取链上 NFT 的拥有者地址 (ownerOf
)通过
fetch
请求自建 API 获取 NFT 详细的链下数据(名称、图片、描述等)页面将链上与链下数据合并显示,提供完整 NFT 详情
2️⃣ Vue 3 实现链上状态与链下数据结合示例
<template>
<div v-if="error" class="error">错误: {{ error }}</div>
<div v-else-if="!owner || !offChainData">加载中...</div>
<div v-else>
<h1>{{ offChainData.name }}</h1>
<img :src="offChainData.image" :alt="offChainData.name" width="300" />
<p>描述: {{ offChainData.description }}</p>
<p>链上拥有者: {{ owner }}</p>
<p>更多链下信息: {{ offChainData.metadata }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ethers } from 'ethers'
const props = defineProps({
tokenId: {
type: Number,
required: true
}
})
const owner = ref(null)
const offChainData = ref(null)
const error = ref(null)
// 合约ABI
const abi = [
"function ownerOf(uint256 tokenId) view returns (address)"
]
const contractAddress = '0xYourContractAddressHere'
async function fetchOffChainData(tokenId) {
const res = await fetch(`/api/nft/${tokenId}`)
if (!res.ok) throw new Error('链下数据获取失败')
return res.json()
}
async function loadData(tokenId) {
try {
if (!window.ethereum) {
error.value = '请安装MetaMask钱包'
return
}
const provider = new ethers.providers.Web3Provider(window.ethereum)
const contract = new ethers.Contract(contractAddress, abi, provider)
owner.value = await contract.ownerOf(tokenId)
offChainData.value = await fetchOffChainData(tokenId)
} catch (e) {
error.value = e.message
}
}
onMounted(() => {
loadData(props.tokenId)
})
watch(() => props.tokenId, (newId) => {
loadData(newId)
})
</script>
代码详解
结合 Vue 的响应式
ref
,用onMounted
和watch
实现数据初始化和动态更新同样通过
ethers.Contract
读取链上所有者,通过 fetch 获取链下数据模板中将链上和链下数据合并展示
额外说明
链上读取使用客户端钱包提供的 provider,数据真实且防篡改
链下数据通过 API 获取,便于存储复杂、富媒体内容和扩展信息
结合后端数据库,可实现完整业务逻辑(用户行为记录、评论、链上事件索引等)
实际项目中可考虑缓存机制、错误处理、加载状态优化
也可以用服务器端渲染(SSR)提前获取链下数据,提高 SEO 和首屏速度