以太坊DApp开发入门示例6,构建一个简单的投票DApp

在前几个入门示例中,我们了解了以太坊账户、智能合约开发、Truffle框架以及前端交互的基础知识,本示例将带大家构建一个简单的投票DApp,涵盖智能合约设计、合约部署、前端界面开发及完整交互流程,这个示例将帮助开发者巩固Solidity编程、Truffle部署流程和Web3.js前端交互,同时理解DApp中“合约-前端-用户”的协同工作模式。

项目准备与环境搭建

在开始之前,请确保已完成以下环境配置:

  1. Node.js(建议版本≥14.0):用于运行前端框架和Truffle工具。
  2. Truffle:全局安装,命令为npm install -g truffle
  3. Ganache:本地以太坊区块链,用于模拟区块链网络(提供10个测试账户,每个账户10000 ETH)。
  4. MetaMask:浏览器钱包插件,用于用户签名交易和管理私钥。
  5. 前端框架:本示例使用HTML + CSS + JavaScript(原生),也可结合React/Vue简化开发。

创建项目目录并初始化:

mkdir ethereum-voting-dapp
cd ethereum-voting-dapp
truffle init
npm install web3

智能合约设计:Voting.sol

投票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:迁移合约

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);
};

部署合约到本地网络

  1. 启动Ganache(默认地址为http://127.0.0.1:7545)。
  2. 在项目目录下运行Truffle部署命令:
    truffle migrate --network development

    成功后,Ganache会显示交易详情,控制台会输出合约部署地址(如Voting deployed at: 0x123...)。

前端开发:连接合约与用户界面

创建前端文件结构

在项目根目录下创建src/文件夹,并添加以下文件:

  • index.html:投票界面。
  • style.css:样式文件。
  • app.js:前端逻辑(连接Web3、调用合约)。

编写HTML界面(index.html)

<!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="vote
随机配图
Button" 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>

编写样式文件(style.css)

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;
}

编写前端逻辑(app.js)

核心功能包括:连接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",

本文由用户投稿上传,若侵权请提供版权资料并联系删除!