以太坊dApp开发教程(如何一步步构造一个全栈式去中心化应用)(三)网页客户端显示候选者

一、改进智能合约

把原来的Election.sol更新为以下代码:

pragma solidity ^0.4.2;

contract Election {
	//候选者结构体
	struct Candidate {
		uint id;
		string name;
		uint voteCount;
	}
	
	//候选者id到结构体的映射
	mapping(uint => Candidate) public candidates;
	
	//总共多少候选者
	uint public candidatesCount;
	
	//构造函数
	constructor() public { 
		addCandidate("Candidate 1");
		addCandidate("Candidate 2");
	}
	
	//添加候选者
	function addCandidate(string _name) private {
		candidatesCount ++;
		candidates[candidatesCount] = Candidate(candidatesCount,_name,0);
	}
}

新的合约默认设置了两个候选者,CTRL+C退出truffle客户端,重新部署合约

 Election合约的地址改变了:0x0ba99ee0fea787336b1e2fe69ce1ed3a4ed94d5b

观察ganache以太坊客户端,可以看到又生产了4个区块,打开第7个区块,可以看到与新部署的合约对应

二、测试合约

我们需要测试合约初始化是否正常,测试很重要,因为:

1. 以太坊区块链上的代码不可改变; 如果合约有问题,需要部署新的合约,新的合约和旧合约状态不同,地址也不同。

2. 部署合约要付出代价(gas),因为部署创建了transaction,并且写数据到区块链中,这会消耗以太币,我们需要最小化成本。

3. 如果合约的方法存在问题,调用者可能浪费以太币。

我们创建测试文件,写以下代码:

var Election = artifacts.require("./Election.sol");

contract("Election", function(accounts) {
  var electionInstance;

  it("initializes with two candidates", function() {
    return Election.deployed().then(function(instance) {
      return instance.candidatesCount();
    }).then(function(count) {
      assert.equal(count, 2);
    });
  });

  it("it initializes the candidates with the correct values", function() {
    return Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.candidates(1);
    }).then(function(candidate) {
      assert.equal(candidate[0], 1, "contains the correct id");
      assert.equal(candidate[1], "Candidate 1", "contains the correct name");
      assert.equal(candidate[2], 0, "contains the correct votes count");
      return electionInstance.candidates(2);
    }).then(function(candidate) {
      assert.equal(candidate[0], 2, "contains the correct id");
      assert.equal(candidate[1], "Candidate 2", "contains the correct name");
      assert.equal(candidate[2], 0, "contains the correct votes count");
    });
  });
});

首先,我们需要合约并且把它赋值给一个变量,就像migration文件里一样。然后我们调用contract方法,把我们的测试写在回调函数里,这些回调函数提供 "accounts"变量以代表ganache客户端里的所有账户。

第一个测试检查合约初始化的候选者数量是否为2,第二个测试检查每个候选者的id,name,vote值是否正常。

运行测试后,结果正常:

至此,我们更新后的合约,初始化时有两个候选者,并且通过了测试。

三、客户端网页编写

现在我们开始编写与智能合约交互的客户端应用,我们修改Truffle Pet Shop box自带的HTML and Javascript文件示例.

 the Bootstrap framework Bootstrap 是最受欢迎的 HTML、CSS 和 JS 框架,用于开发响应式布局、移动设备优先的 WEB 项目。我们可以直接使用 Bootstrap 提供的 CSS 样式表。

lite-server, 轻量级的仅适用于开发 的 node 服务器,它仅支持 web app.

JavaScript的代码不是最重要的,最重要的是智能合约。修改后的src文件夹下的index.html如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Election Results</title>

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container" style="width: 650px;">
      <div class="row">
        <div class="col-lg-12">
          <h1 class="text-center">Election Results</h1>
          <hr/>
          <br/>
          <div id="loader">
            <p class="text-center">Loading...</p>
          </div>
          <div id="content" style="display: none;">
            <table class="table">
              <thead>
                <tr>
                  <th scope="col">#</th>
                  <th scope="col">Name</th>
                  <th scope="col">Votes</th>
                </tr>
              </thead>
              <tbody id="candidatesResults">
              </tbody>
            </table>
            <hr/>
            <p id="accountAddress" class="text-center"></p>
          </div>
        </div>
      </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
    <script src="js/web3.min.js"></script>
    <script src="js/truffle-contract.js"></script>
    <script src="js/app.js"></script>
  </body>
</html>

修改election/src/js/app.js如下:

App = {
  web3Provider: null,
  contracts: {},
  account: '0x0',

  init: function() {
    return App.initWeb3();
  },

  initWeb3: function() {
    if (typeof web3 !== 'undefined') {
      // If a web3 instance is already provided by Meta Mask.
      App.web3Provider = web3.currentProvider;
      web3 = new Web3(web3.currentProvider);
    } else {
      // Specify default instance if no web3 instance provided
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
      web3 = new Web3(App.web3Provider);
    }
    return App.initContract();
  },

  initContract: function() {
    $.getJSON("Election.json", function(election) {
      // Instantiate a new truffle contract from the artifact
      App.contracts.Election = TruffleContract(election);
      // Connect provider to interact with contract
      App.contracts.Election.setProvider(App.web3Provider);

      return App.render();
    });
  },

  render: function() {
    var electionInstance;
    var loader = $("#loader");
    var content = $("#content");

    loader.show();
    content.hide();

    // Load account data
    web3.eth.getCoinbase(function(err, account) {
      if (err === null) {
        App.account = account;
        $("#accountAddress").html("Your Account: " + account);
      }
    });

    // Load contract data
    App.contracts.Election.deployed().then(function(instance) {
      electionInstance = instance;
      return electionInstance.candidatesCount();
    }).then(function(candidatesCount) {
      var candidatesResults = $("#candidatesResults");
      candidatesResults.empty();

      for (var i = 1; i <= candidatesCount; i++) {
        electionInstance.candidates(i).then(function(candidate) {
          var id = candidate[0];
          var name = candidate[1];
          var voteCount = candidate[2];

          // Render candidate Result
          var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
          candidatesResults.append(candidateTemplate);
        });
      }

      loader.hide();
      content.show();
    }).catch(function(error) {
      console.warn(error);
    });
  }
};

$(function() {
  $(window).load(function() {
    App.init();
  });
});

稍微解释下以上代码:

  1. Set up web3: web3.js 是一个帮助客户端应用与区块链交互的javascript库,在"initWeb3"函数中配置web3.
  2. Initialize contracts: 我们在initContract函数中获取部署的Election智能合约实例,然后保存一些需要进行交互的变量。 
  3. Render function: 这个函数利用从智能合约获取的数据对页面进行布局,目前我们只是列出合约中初始化的两个候选者,通过循环合约中映射表来呈现到前台,我们也获取当前连接到区块链的账户并显示出来。

四、运行客户端

在git bash中输入命令 npm run dev,自动弹出网页

网页一直处于"Loading..."状态因为我们没有用账户登录到区块链,为了连接到区块链,我们需要把ganache客户端的一个账户导入到Metamask.

MetaMask插件,可以让你用现有的浏览器,运行基于以太坊的dApp,并且,不用下载全部的、庞大的以太坊节点数据。

chrome浏览器安装教程: https://blog.csdn.net/niumenglong1/article/details/80795796

安装完metamask,并且注册好账户之后,打开我们的election客户端界面,点击右上角的插件,登录后点一下main network,选择customer rpc

在设置中输入ganache客户端的地址: http://localhost:7545   即可在网页中看到两个候选者信息,以及当前登录账户

我们还可以导入新账户,点击import account,导入ganache私有客户端中创建的测试账户的私钥,刷新后可以观察到your account的变化。

比如复制第2个账户的私钥,导入后,刷新以下后可以看到网页变化

猜你喜欢

转载自blog.csdn.net/u011680118/article/details/82423829