铸造页是 NFT 项目的"门面"——好的铸造体验能提升 200% 的转化率。
18.3.1 铸造页架构
graph TD
User[用户] --> UI[铸造UI]
UI --> Check[检查: 白名单? 余额? 供应量?]
Check --> |通过| Tx[构造交易: mint(quantity)]
Check --> |不通过| Error[显示错误/提示]
Tx --> MM[MetaMask 签名]
MM --> Pending[交易 pending]
Pending --> Confirm[确认/失败]
Confirm --> Success[显示铸造结果+链接]
style Check fill:#fff3e0
style Success fill:#e8f5e9
18.3.2 前端代码
tsx
// components/Minter.tsx
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import MyNFT from '../abi/MyNFT.json';
const CONTRACT_ADDRESS = "0x...";
const MINT_PRICE = ethers.parseEther("0.01");
export const Minter = () => {
const [quantity, setQuantity] = useState(1);
const [supply, setSupply] = useState({ total: 0, max: 10000 });
const [price, setPrice] = useState(MINT_PRICE);
const [loading, setLoading] = useState(false);
const [txHash, setTxHash] = useState('');
const provider = new ethers.BrowserProvider(window.ethereum);
useEffect(() => {
const loadSupply = async () => {
const contract = new ethers.Contract(CONTRACT_ADDRESS, MyNFT, await provider.getSigner());
const [minted, max, mintPrice] = await Promise.all([
contract.totalMinted(),
contract.maxSupply(),
contract.mintPrice(),
]);
setSupply({ total: Number(minted), max: Number(max) });
setPrice(mintPrice);
};
loadSupply();
}, []);
const mint = async () => {
setLoading(true);
try {
const signer = await provider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, MyNFT, signer);
const tx = await contract.mint(quantity, {
value: price * BigInt(quantity),
});
setTxHash(tx.hash);
await tx.wait();
// 刷新供应量
const newTotal = await contract.totalMinted();
setSupply(s => ({ ...s, total: Number(newTotal) }));
} finally {
setLoading(false);
}
};
const remaining = supply.max - supply.total;
return (
<div className="minter">
<h2>Mint Your NFT</h2>
<div className="supply">{supply.total} / {supply.max} 已铸造</div>
<div className="quantity-selector">
<button onClick={() => setQuantity(Math.max(1, q-1))}>-</button>
<span>{quantity}</span>
<button onClick={() => setQuantity(Math.min(10, q+1))}>+</button>
</div>
<div className="price">总计: {ethers.formatEther(price * BigInt(quantity))} ETH</div>
<button
onClick={mint}
disabled={loading || remaining <= 0}
className="mint-btn"
>
{loading ? '铸造中...' : remaining <= 0 ? '已售完' : '立即铸造'}
</button>
{txHash && (
<a href={`https://etherscan.io/tx/${txHash}`} target="_blank">
查看交易 →
</a>
)}
</div>
);
};18.3.3 UX 最佳实践
| 场景 | 处理方式 |
|---|---|
| 未连接钱包 | 显示"连接钱包"按钮 |
| 余额不足 | 禁用按钮,提示 ETH 余额 |
| 已售完 | 按钮变灰,显示"已售完" |
| 网络错误 | 显示"请切换到正确网络" |
| 交易失败 | 显示错误信息,允许重试 |
| 铸造成功 | 弹出成功动画,提供 OpenSea 链接 |
, 前往 → 18.4 元数据 |*
评论
0评论加载中…