:2026-04-08 6:48 点击:1
在前几个入门示例中,我们了解了以太坊账户、智能合约开发、Truffle框架以及前端交互的基础知识,本示例将带大家构建一个简单的投票DApp,涵盖智能合约设计、合约部署、前端界面开发及完整交互流程,这个示例将帮助开发者巩固Solidity编程、Truffle部署流程和Web3.js前端交互,同时理解DApp中“合约-前端-用户”的协同工作模式。
在开始之前,请确保已完成以下环境配置:
npm install -g truffle。 HTML + CSS + JavaScript(原生),也可结合React/Vue简化开发。 创建项目目录并初始化:
mkdir ethereum-voting-dapp cd ethereum-voting-dapp truffle init npm install web3
投票DApp的核心是记录投票选项、统计票数、限制投票权限的智能合约,我们将其命名为Voting.sol,放在contracts/目录下。
// contracts/Voting.sol
pragma solidity ^0.8.0;
contract Voting {
// 候选人结构体:姓名和票数
struct Candidate {
string name;
uint voteCount;
}
// 候选人列表(固定数组,可根据需求改为动态数组)
Candidate[] public candidates;
// 记录地址是否已投票
mapping(address => bool) public hasVoted;
// 合约所有者(可结束投票)
address public owner;
// 构造函数:初始化候选人
constructor(string[] memory candidateNames) {
owner = msg.sender;
for (uint i = 0; i < candidateNames.length; i++) {
candidates.push(Candidate({
name: candidateNames[i],
voteCount: 0
}));
}
}
// 投票函数
function vote(uint candidateIndex) public {
require(!hasVoted[msg.sender], "You have already voted!");
require(candidateIndex < candidates.length, "Invalid candidate index!");
hasVoted[msg.sender] = true;
candidates[candidateIndex].voteCount++;
}
// 获取候选人票数
function getVoteCount(uint candidateIndex) public view returns (uint) {
require(candidateIndex < candidates.length, "Invalid candidate index!");
return candidates[candidateIndex].voteCount;
}
// 结束投票(仅所有者可调用)
function endVoting() public {
require(msg.sender == owner, "Only owner can end voting!");
// 这里可以添加逻辑锁定投票(如设置状态变量),示例中简单清空候选人列表
for (uint i = 0; i < candidates.length; i++) {
candidates.pop();
}
}
}
Truffle通过migrations/目录下的脚本管理合约部署,创建2_deploy_contracts.js文件:
// migrations/2_deploy_contracts.js
const Voting = artifacts.require("Voting");
module.exports = function (deployer) {
// 部署合约时传入候选人列表
const candidateNames = ["Alice", "Bob", "Charlie"];
deployer.deploy(Voting, candidateNames);
};
http://127.0.0.1:7545)。 truffle migrate --network development
成功后,Ganache会显示交易详情,控制台会输出合约部署地址(如Voting deployed at: 0x123...)。
在项目根目录下创建src/文件夹,并添加以下文件:
index.html:投票界面。 style.css:样式文件。 app.js:前端逻辑(连接Web3、调用合约)。 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">以太坊投票DApp</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>投票系统</h1>
<div class="voting-section">
<h2>选择候选人:</h2>
<div id="candidates"></div>
<button id="voteButton" disabled>投票</button>
</div>
<div class="results-section">
<h2>投票结果:</h2>
<div id="results"></div>
</div>
<div class="status" id="status"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/web3@1.8.0/dist/web3.min.js"></script>
<script src="app.js"></script>
</body>
</html>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
.voting-section, .results-section {
margin: 20px 0;
}
.candidate {
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.candidate.selected {
background-color: #e6f7ff;
border-color: #1890ff;
}
button {
width: 100%;
padding: 10px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:disabled {
background-color: #d9d9d9;
cursor: not-allowed;
}
.status {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
text-align: center;
}
.status.success {
background-color: #f6ffed;
color: #52c41a;
}
.status.error {
background-color: #fff2f0;
color: #ff4d4f;
}
核心功能包括:连接Web3、加载候选人列表、监听用户投票、实时更新结果。
// src/app.js
document.addEventListener('DOMContentLoaded', () => {
// 1. 初始化Web3
let web3;
let votingContract;
let accounts;
if (typeof window.ethereum !== 'undefined') {
web3 = new Web3(window.ethereum);
window.ethereum.request({ method: 'eth_requestAccounts' })
.then(acc => {
accounts = acc;
initApp();
})
.catch(err => console.error("连接钱包失败:", err));
} else {
document.getElementById('status').innerHTML =
"<div class='status error'>请安装MetaMask钱包!</div>";
}
// 2. 初始化应用
async function initApp() {
const networkId = await web3.eth.net.getId();
const contractAddress = "0x123..."; // 替换为实际部署的合约地址
const contractAbi = [
// 这里粘贴Voting.sol的ABI(可通过truffle compile生成)
{
"inputs": [],
"name": "candidates",
"outputs": [
{"internalType": "string", "name": "name", "type": "string"},
{"internalType": "uint256",
本文由用户投稿上传,若侵权请提供版权资料并联系删除!