整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

简单的html网页(社保计算器)

lt;!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>个人社保计算器--网页版</title>

<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>

<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>

<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<script><!--脚本-->

function calc(){

//1.把用户输入的工资拿到

//jQuery函数

sal = $("#salary").val();

//2.把sal转成数字

salnum = parseInt(sal);

//3.开始计算

//3.1 算各种数据

ylgr = salnum * 0.08;

ylgs = salnum * 0.2;

ybgr = salnum * 0.02;

ybgs = salnum * 0.06;

sygr = salnum * 0.0005;

sygs = salnum * 0.0015;

gsgs = salnum * 0.0005;

sybx = salnum * 0.0008;

gjjgr = salnum * 0.12;

gjjgs = salnum * 0.12;

grhj = ylgr+ybgr+sygr+gjjgr;

gshj = ylgs+ybgs+sygs+gsgs+sybx+gjjgs;

total = grhj+gshj;

//4.把计算结果放到格子里

$("#ylgr").text(ylgr);

$("#ylgs").text(ylgs);

$("#ybgr").text(ybgr);

$("#ybgs").text(ybgs);

$("#sygr").text(sygr);

$("#sygs").text(sygs);

$("#gsgs").text(gsgs);

$("#sybx").text(sybx);

$("#gjjgr").text(gjjgr);

$("#gjjgs").text(gjjgs);

$("#grhj").text(grhj);

$("#gshj").text(gshj);

$("#total").text(total);


}

</script>

</head>

<body>

<div class="container text-center">

<h1>个人社保计算器</h1>

<table class="table table-bordered">

<tr>

<td>工资</td>

<td colspan="3">

<input id="salary" class="form-control" placeholder="请输入工资" type="text" >

</td>

<td>

<button onclick="calc()" class="btn btn-danger btn-block">计算</button>

</td>

</tr>

<tr class="bg-primary">

<td>险种</td>

<td>个人%</td>

<td>个人</td>

<td>公司%</td>

<td>公司</td>

</tr>

<tr>

<td>养老</td>

<td>8%</td>

<td id="ylgr"></td>

<td>20%</td>

<td id="ylgs"></td>

</tr>

<tr>

<td>医保</td>

<td>2%</td>

<td id="ybgr"></td>

<td>6%</td>

<td id="ybgs"></td>

</tr>

<tr>

<td>失业</td>

<td>0.5%</td>

<td id="sygr"></td>

<td>1.5%</td>

<td id="sygs"></td>

</tr>

<tr>

<td>工伤</td>

<td></td>

<td></td>

<td>0.5%</td>

<td id="gsgs"></td>

</tr>

<tr>

<td>生育</td>

<td></td>

<td></td>

<td>0.8%</td>

<td id="sybx"></td>

</tr>

<tr>

<td>公积金</td>

<td>12%</td>

<td id="gjjgr"></td>

<td>12%</td>

<td id="gjjgs"></td>

</tr>

<tr>

<td>合计</td>

<td>个人合计</td>

<td id="grhj"></td>

<td>公司合计</td>

<td id="gshj"></td>

</tr>

<tr>

<td>总额</td>

<td colspan="4" id="total"></td>

</tr>

</table>

开发者:一字赌团队2021

</div>

</body>

</html>

本教程中,您将学习如何开发一个具有利息累加、削减和存款/取款功能的简单质.押. DApp。

在过去课程中,我们学习了很多不同的Solidity和Javascript原语,这些原语为我们提供了Web3开发的基本构件。

我们学会了如何从头开始使用Hardhat,建立我们自己的前台,甚至编写Solidity。

虽然所有这些技能对于希望建立坚实基础的开发者来说都是非常有价值的,但也有一些工具可以帮助抽象出一些复杂的环境设置和依赖关系,使构建者可以更容易地进行修补

我们推荐的这些工具之一是Scaffold-eth[1]!

Scaffold-eth的核心是为以太坊上的快速原型开发提供了一个现成的堆栈,使开发者能够获得最先进的工具来快速学习/交付基于以太坊的dApp。

使用Scaffold-eth和Alchemy,你可以轻松地在区块链上合成和部署代码。

在本教程中,我们将使用SpeedRunEthereum[2] Challenge #1[3]的基础代码,并且共同构建一个简单的质.押. dApp。

如果你不熟悉加密货币质.押.,它最好被概括为将持有的加密货币锁定/存入DeFi协议或智能合约以获得利息的过程。

质.押.加密货币已成为许多DeFi协议的基石,并允许开发人员创建复杂的金融衍生产品。

虽然大多数DeFi质.押.合同都非常复杂,但我们将通过一个最基本的合同来学习关键概念。

我们将一起学习以下有关质.押.的构件。

  1. 用Scaffold-Eth构建:• 一起破解前端 • 打造 Solidity “后端”
  2. 将ETH从钱包转移到智能合约,反之亦然
  3. 利用Solidity修改器

让我们从了解Scaffold-Eth的工作原理开始吧!

1. 下载Scaffold-Eth

在本教程中,我们将使用Scaffold-Eth开发者环境来制作我们的智能合约,并将我们的前端UI放在一起。

在开始之前,我想传达几个重要的细节,让大家牢记在心!

Scaffold-Eth在抽象化环境设置和前端依赖方面非常棒,这使它成为一个强大的工具。

虽然有很多功能是由Scaffold-Eth自动处理的,但当你对整个开发者环境有了更扎实的掌握后,深入到代码中去了解其中一些功能是如何产生的,这一点很重要。

让我们从Fork SpeedRunEthereum[4]Challenge #1[5] 的基础代码库开始。

git clone https://github.com/scaffold-eth/scaffold-eth-challenges.git challenge-1-decentralized-staking

cd challenge-1-decentralized-staking

git checkout challenge-1-decentralized-staking

yarn install

如果你已经成功跟上,你将能够在你的基础文件目录中出现一个名为challenge-1-decentralized-staking的新文件夹。

在运行上述命令后,我们留下了一个充满文件的大文件夹。

即使在继续学习代码之前,我们也应该熟悉Scaffold-Eth中关键文件的存储位置,这样我们就知道应该把重点放在哪里。

在本教程中,我们将主要在Staker.solApp.jsx上工作。

2. 设置你的环境

接下来,你需要为以下三个命令建立三个独立的终端。

启动你的React前端。

yarn start

启动你的Hardhat后端。

yarn chain

编译、部署和发布你的package/contracts文件中的所有合同。

yarn deploy

每当你更新你的合同时,运行yarn deploy --reset来 "刷新 "Scaffold-Eth中的合同。

很好! 现在你应该可以在http://localhost:3000/,看到这个仓库的UI前台了。

3. 熟悉Scaffold-Eth

虽然我知道你很想开始写代码,但还有一些细节需要处理

在我们的默认视图中,我们有两个标签--Staker UI & Debug Contracts

来吧,在你的前端页面上来回切换,看看不同的功能。

Staker UI包含了所有我们将主要与之互动的前端组件。

如果你点击所提供的按钮,你会发现大多数按钮还没有完全连接起来,你会立即遇到错误。

看一下Staker UI。你会注意到它是如此的简陋的! 这就是我们主要要改变的东西。

由于以太坊上的任何链上互动都需要testnet ETH,所以你需要本地testnet ETH来开始砍价。

首先,抓住你的本地主机钱包地址。

点击右上角的 "复制 "按钮。

接下来,前往左下角。你将能够在这里访问本地龙头。

  • • 要么在打开的字段中复制/粘贴你的地址,要么点击 "钱包 "图标
  • • 在扩展视图中粘贴您的地址
  • • 给自己发送一些测试ETH

在为你的本地钱包充值后,你就可以与你的合约进行互动了

第二个标签,Debug Contracts,是另一个前端显示,它包含了Scaffold-Eth的一个超级大功能!在这个标签中,你可以看到你的合同。

一旦你yarn deploy你的合约,并配置它正确地读取合约数据,它将自动生成一个裸露的UI,允许你与你的合约功能互动。

例如,在下面的例子中,我们可以通过我们的智能合约读取和写入信息,只需放入参数并点击 "发送"。有了Scaffold-Eth,我们不需要只使用CLI命令,而是有一个更直观的原型设计方式。

如果你想在Debug Contracts 标签页中存储和查看一个变量,请确保将该变量设置为public!

棒极了!

现在我们已经熟悉了Scaffold-Eth,我们可以更深入地研究代码了。

4. 潜入Solidity

看一下我们的Staker.sol文件,我们发现我们有一个相当空的Solidity文件,里面有一堆注释,说明需要填写的内容。

由于教程偏离了Scaffold-Eth的 Challenge #1,我们可以忽略这些注释,从以下代码开始。

pragma solidity >=0.6.0 <0.7.0;

import "hardhat/console.sol";
import "./ExampleExternalContract.sol";

contract Staker {
  ExampleExternalContract public exampleExternalContract;
  
  constructor(address exampleExternalContractAddress) public {
      exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
  }
  
}

项目参数

在写出我们的智能合约代码之前,让我们来看看我们期望我们的质.押.DApp如何工作

  1. 1. 为了简单起见,我们只希望有一个用户与我们的质.押.DApp互动
  2. 2. 我们需要能够从Staker合约中存款和取款。• 质.押.是一个一次性的行为,这意味着一旦我们质.押.,就不能再重新质.押.。• 从合约中取款会移除整个本金余额和任何应计利息
  3. 3. Staker合约的利息支付率为每秒钟0.1个ETH,存入的ETH有资格获得利息累积。
  4. 4. 合同部署后,Staker合同应该从Hh2时间戳计数器开始。第一个期限应设置为2分钟,第二个期限设置为4分钟• 2分钟的期限决定了钉子户能够存入资金的时期。(在t=0分钟和t=2分钟之间,质.押.用户可以存款)• 从存入资金到2分钟期限之间发生的所有区块都是有效的应计利息。• 在2分钟的提款期限过后,质.押.用户可以提取全部本金余额和任何应计利息,直到4分钟的期限到来。• 在额外的2分钟提款窗口过后,用户被阻止提取他们的资金,因为他们已经超时了。
  5. 5. 如果质.押.用户还有资金,我们可以调用最后一个函数,将资金 "锁定 "在已经预装在Scaffold-Eth环境中的外部合同中,即ExampleExternalContract.sol。

虽然上面列出的质.押.参数可能看起来有点复杂,但许多现实生活中的质.押.dApps都有类似的基元,用户有一个有限的存款和提款期。

而且,许多DApps将抑制 "非生产性 "资本,这些资本在质.押.期结束后只是闲置在那里。

有时,DeFi协议甚至可能在等待期结束后吸收未付的存款,这与我们在教程中所说的最后一个参数类似。

Solidity Mappingss

在我们的智能合约中,我们将需要两个映射来帮助我们存储一些数据。

特别是,我们需要一些东西来跟踪。

  1. 1. 有多少ETH被存入合约中
  2. 2. 存款发生的时间

我们可以通过以下方式实现这一目标。

mapping(address => uint256) public balances; 
mapping(address => uint256) public depositTimestamps;

公共变量

根据上面列出的准则[6],我们还需要一些不同的变量。

uint256 public constant rewardRatePerSecond = 0.1 ether; 
uint256 public withdrawalDeadline = block.timestamp + 120 seconds; 
uint256 public claimDeadline = block.timestamp + 240 seconds; 
uint256 public currentBlock = 0;

奖励率设定了质.押.本金的ETH的发放利率。

提款和索赔的最后期限帮助我们设定质.押.机制开始/结束的最后期限。

最后,我们有一个变量,用来保存当前区块。

我们使用block.timestamp + XXX seconds来创建最后期限,正好是我们的合约启动后的XXX秒。作为一种计时机制,这肯定有点 "天真";你能想出更好的方法来实现这一点,例如,它更具有通用性?

事件

尽管我们不会将事件推送到我们的前端,但我们仍然应该确保我们在合同的关键部分发出事件,以确保我们保持最佳的编程实践。

event Stake(address indexed sender, uint256 amount); 
event Received(address, uint); 
event Execute(address indexed sender, uint256 amount);

现在我们已经锁定了关键参数/变量,我们可以继续制作我们将在教程中使用的具体函数。

只读的时间函数

正如项目参数中所述,许多不同的质.押.DApp的功能都受制于 "时间锁",在特定的时间点启用/禁止某些行动。

在这里,我们有两个不同的功能来管理提款窗口的开始和结束。

  function withdrawalTimeLeft() public view returns (uint256 withdrawalTimeLeft) {
    if( block.timestamp >= withdrawalDeadline) {
      return (0);
    } else {
      return (withdrawalDeadline - block.timestamp);
    }
  }

  function claimPeriodLeft() public view returns (uint256 claimPeriodLeft) {
    if( block.timestamp >= claimDeadline) {
      return (0);
    } else {
      return (claimDeadline - block.timestamp);
    }
  }

这两个函数在设计上其实都非常熟悉。

它们都有一个标准的if -> else语句。

条件只是检查当前时间是否大于或小于公共变量部分[7]规定的最后期限。

如果当前时间大于预先安排的最后期限,我们就知道最后期限已过,并返回0以表示 "状态变化 "已经发生。

否则,我们只是返回在最后期限到来之前的剩余时间。

修改器

对于一个更深入的修改器的例子,请看 Solidity By Example[8]

简而言之,Solidity 修改器是可以在函数调用之前和/或之后运行的代码片段。

虽然它们有许多不同的用途,但最常见和最基本的用例之一是在特定条件未完全满足的情况下限制对某些功能的访问。

在本教程中,我们将精确地使用修改器来帮助对关键功能进行把关,这些功能决定了我们的入股、提款和返还功能。

下面是我们使用的三个修改器。

  modifier withdrawalDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = withdrawalTimeLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Withdrawal period is not reached yet");
    } else {
      require(timeRemaining > 0, "Withdrawal period has been reached");
    }
    _;
  }

  modifier claimDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = claimPeriodLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Claim deadline is not reached yet");
    } else {
      require(timeRemaining > 0, "Claim deadline has been reached");
    }
    _;
  }

  modifier notCompleted() {
    bool completed = exampleExternalContract.completed();
    require(!completed, "Stake already completed!");
    _;
  }

修改器 withdrawalDeadlineReached(bool requireReached) &claimDeadlineReached(bool requireReached)都接受一个布尔参数,并检查以确保其各自的最后期限为真或假。

修改器notCompleted()的操作方式类似,但实际上它的性质更复杂一点,尽管它包含的代码行数更少。

它实际上是从Staker外部的合同中调用一个函数 completed(),并检查它的返回值是真还是假,以确认该标志是否被切换。

现在,让我们在接下来的几个函数上实现我们刚刚创建的修改器,用闸门限制访问。

存款/质.押.功能

在我们的入金函数中,我们使用先前创建的修改器,将withdrawingDeadlineReached()中的参数设置为false,将claimDeadlineReached()设置为false,因为我们不希望这两个期限已经过去。

  // Stake function for a user to stake ETH in our contract
  
  function stake() public payable withdrawalDeadlineReached(false) claimDeadlineReached(false) {
    balances[msg.sender] = balances[msg.sender] + msg.value;
    depositTimestamps[msg.sender] = block.timestamp;
    emit Stake(msg.sender, msg.value);
  }

该函数的其余部分在一个典型的 "存款 "场景中是相当标准的,我们的余额映射被更新以包括送入的资金。

我们还用存款的当前时间来设置我们的存款时间戳,这样我们就可以在以后的利息计算中访问这个存储值。

提款功能

在我们的取款函数中,我们再次使用先前创建的修改器,但这次我们希望drawalDeadlineReached()为真, claimDeadlineReached()为假。

这组修改器/参数意味着我们处于提款窗口的最佳位置,因为提款时间到了,不会有任何处罚,而且我们还能得到利息。

  /*
  Withdraw function for a user to remove their staked ETH inclusive
  of both the principle balance and any accrued interest
  */
  
  function withdraw() public withdrawalDeadlineReached(true) claimDeadlineReached(false) notCompleted{
    require(balances[msg.sender] > 0, "You have no balance to withdraw!");
    uint256 individualBalance = balances[msg.sender];
    uint256 indBalanceRewards = individualBalance + ((block.timestamp-depositTimestamps[msg.sender])*rewardRatePerBlock);
    balances[msg.sender] = 0;

    // Transfer all ETH via call! (not transfer) cc: https://solidity-by-example.org/sending-ether
    (bool sent, bytes memory data) = msg.sender.call{value: indBalanceRewards}("");
    require(sent, "RIP; withdrawal failed :( ");
  }

该函数的其余部分做了几个重要步骤。

  1. 1. 它检查以确保试图提取ETH的人实际上有一个非零的股份。
  2. 2. 它通过计算从存款到取款的区块数,并乘以我们的利息常数,来计算欠下的ETH的利息金额。
  3. 3. 它将用户的余额质.押.ETH设为0,这样就不会发生重复计算。
  4. 4. 它将ETH从智能合约转移回用户的钱包。

执行返还功能

在这里,我们希望 claimDeadlineReached() 为真,因为非生产性资金的返还只能在4分钟后发生。

同样地,我们希望notCompleted为真,因为这个DApp只设计为单一用途。

  /*
  Allows any user to repatriate "unproductive" funds that are left in the staking contract
  past the defined withdrawal period
  */
  
  function execute() public claimDeadlineReached(true) notCompleted {
    uint256 contractBalance = address(this).balance;
    exampleExternalContract.complete{value: address(this).balance}();
  }

其余的功能。

  1. 1. 抓取Staker合约中的ETH的当前余额
  2. 2. 将ETH发送到repo的exampleExternalContract中。

如果你到目前为止一直跟着Solidity走,你的Staker.sol应该是下面这个样子。

// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

import "hardhat/console.sol";
import "./ExampleExternalContract.sol";

contract Staker {

  ExampleExternalContract public exampleExternalContract;

  mapping(address => uint256) public balances;
  mapping(address => uint256) public depositTimestamps;

  uint256 public constant rewardRatePerSecond = 0.1 ether;
  uint256 public withdrawalDeadline = block.timestamp + 120 seconds;
  uint256 public claimDeadline = block.timestamp + 240 seconds;
  uint256 public currentBlock = 0;

  // Events
  event Stake(address indexed sender, uint256 amount);
  event Received(address, uint);
  event Execute(address indexed sender, uint256 amount);

  // Modifiers
  /*
  Checks if the withdrawal period has been reached or not
  */
  modifier withdrawalDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = withdrawalTimeLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Withdrawal period is not reached yet");
    } else {
      require(timeRemaining > 0, "Withdrawal period has been reached");
    }
    _;
  }

  /*
  Checks if the claim period has ended or not
  */
  modifier claimDeadlineReached( bool requireReached ) {
    uint256 timeRemaining = claimPeriodLeft();
    if( requireReached ) {
      require(timeRemaining == 0, "Claim deadline is not reached yet");
    } else {
      require(timeRemaining > 0, "Claim deadline has been reached");
    }
    _;
  }

  /*
  Requires that the contract only be completed once!
  */
  modifier notCompleted() {
    bool completed = exampleExternalContract.completed();
    require(!completed, "Stake already completed!");
    _;
  }

  constructor(address exampleExternalContractAddress){
      exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
  }

  // Stake function for a user to stake ETH in our contract
  function stake() public payable withdrawalDeadlineReached(false) claimDeadlineReached(false){
    balances[msg.sender] = balances[msg.sender] + msg.value;
    depositTimestamps[msg.sender] = block.timestamp;
    emit Stake(msg.sender, msg.value);
  }

  /*
  Withdraw function for a user to remove their staked ETH inclusive
  of both principal and any accrued interest
  */
  function withdraw() public withdrawalDeadlineReached(true) claimDeadlineReached(false) notCompleted{
    require(balances[msg.sender] > 0, "You have no balance to withdraw!");
    uint256 individualBalance = balances[msg.sender];
    uint256 indBalanceRewards = individualBalance + ((block.timestamp-depositTimestamps[msg.sender])*rewardRatePerBlock);
    balances[msg.sender] = 0;

    // Transfer all ETH via call! (not transfer) cc: https://solidity-by-example.org/sending-ether
    (bool sent, bytes memory data) = msg.sender.call{value: indBalanceRewards}("");
    require(sent, "RIP; withdrawal failed :( ");
  }

  /*
  Allows any user to repatriate "unproductive" funds that are left in the staking contract
  past the defined withdrawal period
  */
  function execute() public claimDeadlineReached(true) notCompleted {
    uint256 contractBalance = address(this).balance;
    exampleExternalContract.complete{value: address(this).balance}();
  }

  /*
  READ-ONLY function to calculate the time remaining before the minimum staking period has passed
  */
  function withdrawalTimeLeft() public view returns (uint256 withdrawalTimeLeft) {
    if( block.timestamp >= withdrawalDeadline) {
      return (0);
    } else {
      return (withdrawalDeadline - block.timestamp);
    }
  }

  /*
  READ-ONLY function to calculate the time remaining before the minimum staking period has passed
  */
  function claimPeriodLeft() public view returns (uint256 claimPeriodLeft) {
    if( block.timestamp >= claimDeadline) {
      return (0);
    } else {
      return (claimDeadline - block.timestamp);
    }
  }

  /*
  Time to "kill-time" on our local testnet
  */
  function killTime() public {
    currentBlock = block.timestamp;
  }

  /*
  \Function for our smart contract to receive ETH
  cc: https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function
  */
  receive() external payable {
      emit Received(msg.sender, msg.value);
  }

}

5. 进入前台

太棒了! 我们刚刚经历了一堆Solidity。当涉及到前端显示时,Scaffold-Eth试图让事情变得简单而美好。它包含了很多不同的反应组件,为用户提供了低代码的解决方案,以实现令人敬畏的UI!我鼓励你玩玩这些组件。我鼓励你玩玩不同的组件,但与此同时,我们将在spartan方面学习更多。

看看我们的App.jsx文件,特别是在链接573附近的代码块[9],我们看到一个代码块,用于从我们的Solidity合约中捕获发射的事件,并将其显示为一个列表。

有效地,它允许我们记录从我们的智能合约发射的不同行动,解析存储的信息,然后直观地允许dApp用户查看他们的链上历史。

虽然我们将通过在Solidity合约中发射事件来实践良好的编程标准,但这次,为了简单起见,我们将在前端忽略事件,所以让我们完全删除这个代码块。

如果你看一下你的Staker UI标签,你会发现事件框已经被抹去了。

前台编辑

在大多数情况下,前台的许多代码将保持与默认的相同 在前面的步骤中,我们已经删除了事件反应组件。

我们的最终目标是要有一个漂亮的、简单的用户界面,看起来像下面这样。

请注意,默认的前台缺少一些默认 repo 的视觉元素。

每秒奖励率

为了构建这部分内容,我们在Staker合同块下直接插入以下代码。

  <div style={{ padding: 8, marginTop: 16 }}>
    <div>Reward Rate Per Second:</div>
    <Balance balance={rewardRatePerSecond} fontSize={64} /> ETH
  </div>

在这里,我们利用了Scaffold-Eth的平衡反应组件来帮助格式化!

最后期限的UI元素

为了构建截止日期视觉组件,我们使用了以下2段代码。

  // ** keep track of a variable from the contract in the local React state:
  const claimPeriodLeft = useContractReader(readContracts, "Staker", "claimPeriodLeft");
  console.log("⏳ Claim Period Left:", claimPeriodLeft);

  const withdrawalTimeLeft = useContractReader(readContracts, "Staker", "withdrawalTimeLeft");
  console.log("⏳ Withdrawal Time Left:", withdrawalTimeLeft);
<div style={{ padding: 8, marginTop: 16, fontWeight: "bold" }}>
  <div>Claim Period Left:</div>
  {claimPeriodLeft && humanizeDuration(claimPeriodLeft.toNumber() * 1000)}
</div>

<div style={{ padding: 8, marginTop: 16, fontWeight: "bold"}}>
  <div>Withdrawal Period Left:</div>
  {withdrawalTimeLeft && humanizeDuration(withdrawalTimeLeft.toNumber() * 1000)}
</div>

虽然第二个代码片断很熟悉,与我们已经看到的相似,但第一个代码块有点独特。不是说我们调用 claimPeriodLeft withdrawalTimeLeft来访问前端的存储变量值。

然而,我们实际上必须先从智能合约中读取这些值本身。第一个代码片断处理了这个逻辑!

杂项:对其他现有的UI组件的编辑

现在你已经看到了2个不同的前台UI组件的例子,你能想出如何对前台进行其余的修改,使其看起来像上面提供的样本吗!?

你应该只需要在这里调整一些参数,所以不要想得太多了

对用户界面的自由发挥

虽然Scaffold-Eth有很多默认组件,使用户能够简单地利用 "低代码 "解决方案,但它也使用户能够访问更大的前端库。

默认情况下,它有一个与Ant Design react components(https://ant.design/components/overview/)的挂钩,允许任何人从那里提取组件

在我们的前端样本中,我们实际上看到了 "线 "来划分每个大块的视觉组件。

通过探索Ant Design中的不同选项,可以重现这些线条

如果你想得到提示,请看一下Ant Dividers!

不要忘记,我们需要在App.jsx文件的顶部导入我们计划使用的组件

从 "antd "导入 { Alert, Button, Col, Menu, Row, List, Divider }。

如果你已经跟上了前端的代码,你的App.jsx应该看起来像下面这样。

import WalletConnectProvider from "@walletconnect/web3-provider";
//import Torus from "@toruslabs/torus-embed"
import WalletLink from "walletlink";
import { Alert, Button, Col, Menu, Row, List, Divider } from "antd";
import "antd/dist/antd.css";
import React, { useCallback, useEffect, useState } from "react";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
import Web3Modal from "web3modal";
import "./App.css";
import { Account, Address, Balance, Contract, Faucet, GasGauge, Header, Ramp, ThemeSwitch } from "./components";
import { INFURA_ID, NETWORK, NETWORKS } from "./constants";
import { Transactor } from "./helpers";
import {
  useBalance,
  useContractLoader,
  useContractReader,
  useGasPrice,
  useOnBlock,
  useUserProviderAndSigner,
} from "eth-hooks";
import { useEventListener } from "eth-hooks/events/useEventListener";
import { useExchangeEthPrice } from "eth-hooks/dapps/dex";
// import Hints from "./Hints";
import { ExampleUI, Hints, Subgraph } from "./views";

import { useContractConfig } from "./hooks";
import Portis from "@portis/web3";
import Fortmatic from "fortmatic";
import Authereum from "authereum";
import humanizeDuration from "humanize-duration";

const { ethers } = require("ethers");
/*
    Welcome to  scaffold-eth !

    Code:
    https://github.com/austintgriffith/scaffold-eth

    Support:
    https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA
    or DM @austingriffith on Twitter or Telegram

    You should get your own Infura.io ID and put it in `constants.js`
    (this is your connection to the main Ethereum network for ENS etc.)


     EXTERNAL CONTRACTS:
    You can also bring in contract artifacts in `constants.js`
    (and then use the `useExternalContractLoader()` hook!)
*/

///  What chain are your contracts deployed to?
const targetNetwork = NETWORKS.localhost; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet)

//  Sorry for all the console logging
const DEBUG = true;
const NETWORKCHECK = true;

//  providers
if (DEBUG) console.log(" Connecting to Mainnet Ethereum");
// const mainnetProvider = getDefaultProvider("mainnet", { infura: INFURA_ID, etherscan: ETHERSCAN_KEY, quorum: 1 });
// const mainnetProvider = new InfuraProvider("mainnet",INFURA_ID);
//
// attempt to connect to our own scaffold eth rpc and if that fails fall back to infura...
// Using StaticJsonRpcProvider as the chainId won't change see https://github.com/ethers-io/ethers.js/issues/901
const scaffoldEthProvider = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider("https://rpc.scaffoldeth.io:48544")
  : null;
const poktMainnetProvider = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider(
      "https://eth-mainnet.gateway.pokt.network/v1/lb/611156b4a585a20035148406",
    )
  : null;
const mainnetInfura = navigator.onLine
  ? new ethers.providers.StaticJsonRpcProvider("https://mainnet.infura.io/v3/" + INFURA_ID)
  : null;
// ( ⚠️ Getting "failed to meet quorum" errors? Check your INFURA_ID

//  Your local provider is usually pointed at your local blockchain
const localProviderUrl = targetNetwork.rpcUrl;
// as you deploy to other networks you can set REACT_APP_PROVIDER=https://dai.poa.network in packages/react-app/.env
const localProviderUrlFromEnv = process.env.REACT_APP_PROVIDER ? process.env.REACT_APP_PROVIDER : localProviderUrl;
if (DEBUG) console.log(" Connecting to provider:", localProviderUrlFromEnv);
const localProvider = new ethers.providers.StaticJsonRpcProvider(localProviderUrlFromEnv);

//  block explorer URL
const blockExplorer = targetNetwork.blockExplorer;

// Coinbase walletLink init
const walletLink = new WalletLink({
  appName: "coinbase",
});

// WalletLink provider
const walletLinkProvider = walletLink.makeWeb3Provider(`https://mainnet.infura.io/v3/${INFURA_ID}`, 1);

// Portis ID: 6255fb2b-58c8-433b-a2c9-62098c05ddc9
/*
  Web3 modal helps us "connect" external wallets:
*/
const web3Modal = new Web3Modal({
  network: "mainnet", // Optional. If using WalletConnect on xDai, change network to "xdai" and add RPC info below for xDai chain.
  cacheProvider: true, // optional
  theme: "light", // optional. Change to "dark" for a dark theme.
  providerOptions: {
    walletconnect: {
      package: WalletConnectProvider, // required
      options: {
        bridge: "https://polygon.bridge.walletconnect.org",
        infuraId: INFURA_ID,
        rpc: {
          1: `https://mainnet.infura.io/v3/${INFURA_ID}`, // mainnet // For more WalletConnect providers: https://docs.walletconnect.org/quick-start/dapps/web3-provider#required
          42: `https://kovan.infura.io/v3/${INFURA_ID}`,
          100: "https://dai.poa.network", // xDai
        },
      },
    },
    portis: {
      display: {
        logo: "https://user-images.githubusercontent.com/9419140/128913641-d025bc0c-e059-42de-a57b-422f196867ce.png",
        name: "Portis",
        description: "Connect to Portis App",
      },
      package: Portis,
      options: {
        id: "6255fb2b-58c8-433b-a2c9-62098c05ddc9",
      },
    },
    fortmatic: {
      package: Fortmatic, // required
      options: {
        key: "pk_live_5A7C91B2FC585A17", // required
      },
    },
    // torus: {
    //   package: Torus,
    //   options: {
    //     networkParams: {
    //       host: "https://localhost:8545", // optional
    //       chainId: 1337, // optional
    //       networkId: 1337 // optional
    //     },
    //     config: {
    //       buildEnv: "development" // optional
    //     },
    //   },
    // },
    "custom-walletlink": {
      display: {
        logo: "https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0",
        name: "Coinbase",
        description: "Connect to Coinbase Wallet (not Coinbase App)",
      },
      package: walletLinkProvider,
      connector: async (provider, _options) => {
        await provider.enable();
        return provider;
      },
    },
    authereum: {
      package: Authereum, // required
    },
  },
});

function App(props) {
  const mainnetProvider =
    poktMainnetProvider && poktMainnetProvider._isProvider
      ? poktMainnetProvider
      : scaffoldEthProvider && scaffoldEthProvider._network
      ? scaffoldEthProvider
      : mainnetInfura;

  const [injectedProvider, setInjectedProvider] = useState();
  const [address, setAddress] = useState();

  const logoutOfWeb3Modal = async () => {
    await web3Modal.clearCachedProvider();
    if (injectedProvider && injectedProvider.provider && typeof injectedProvider.provider.disconnect == "function") {
      await injectedProvider.provider.disconnect();
    }
    setTimeout(() => {
      window.location.reload();
    }, 1);
  };

  /*  This hook will get the price of ETH from  Uniswap: */
  const price = useExchangeEthPrice(targetNetwork, mainnetProvider);

  /*  This hook will get the price of Gas from ⛽️ EtherGasStation */
  const gasPrice = useGasPrice(targetNetwork, "fast");
  // Use your injected provider from  Metamask or if you don't have it then instantly generate a  burner wallet.
  const userProviderAndSigner = useUserProviderAndSigner(injectedProvider, localProvider);
  const userSigner = userProviderAndSigner.signer;

  useEffect(() => {
    async function getAddress() {
      if (userSigner) {
        const newAddress = await userSigner.getAddress();
        setAddress(newAddress);
      }
    }
    getAddress();
  }, [userSigner]);

  // You can warn the user if you would like them to be on a specific network
  const localChainId = localProvider && localProvider._network && localProvider._network.chainId;
  const selectedChainId =
    userSigner && userSigner.provider && userSigner.provider._network && userSigner.provider._network.chainId;

  // For more hooks, check out eth-hooks at: https://www.npmjs.com/package/eth-hooks

  // The transactor wraps transactions and provides notificiations
  const tx = Transactor(userSigner, gasPrice);

  // Faucet Tx can be used to send funds from the faucet
  const faucetTx = Transactor(localProvider, gasPrice);

  //  scaffold-eth is full of handy hooks like this one to get your balance:
  const yourLocalBalance = useBalance(localProvider, address);

  // Just plug in different  providers to get your balance on different chains:
  const yourMainnetBalance = useBalance(mainnetProvider, address);

  const contractConfig = useContractConfig();

  // Load in your local  contract and read a value from it:
  const readContracts = useContractLoader(localProvider, contractConfig);

  // If you want to make  write transactions to your contracts, use the userSigner:
  const writeContracts = useContractLoader(userSigner, contractConfig, localChainId);

  // EXTERNAL CONTRACT EXAMPLE:
  //
  // If you want to bring in the mainnet DAI contract it would look like:
  const mainnetContracts = useContractLoader(mainnetProvider, contractConfig);

  // If you want to call a function on a new block
  useOnBlock(mainnetProvider, () => {
    console.log(`⛓ A new mainnet block is here: ${mainnetProvider._lastBlockNumber}`);
  });

  // Then read your DAI balance like:
  const myMainnetDAIBalance = useContractReader(mainnetContracts, "DAI", "balanceOf", [
    "0x34aA3F359A9D614239015126635CE7732c18fDF3",
  ]);

  //keep track of contract balance to know how much has been staked total:
  const stakerContractBalance = useBalance(
    localProvider,
    readContracts && readContracts.Staker ? readContracts.Staker.address : null,
  );
  if (DEBUG) console.log(" stakerContractBalance", stakerContractBalance);

  const rewardRatePerSecond = useContractReader(readContracts, "Staker", "rewardRatePerSecond");
  console.log(" Reward Rate:", rewardRatePerSecond);

  // ** keep track of a variable from the contract in the local React state:
  const balanceStaked = useContractReader(readContracts, "Staker", "balances", [address]);
  console.log(" balanceStaked:", balanceStaked);

  // **  Listen for broadcast events
  const stakeEvents = useEventListener(readContracts, "Staker", "Stake", localProvider, 1);
  console.log(" stake events:", stakeEvents);

  const receiveEvents = useEventListener(readContracts, "Staker", "Received", localProvider, 1);
  console.log(" receive events:", receiveEvents);

  // ** keep track of a variable from the contract in the local React state:
  const claimPeriodLeft = useContractReader(readContracts, "Staker", "claimPeriodLeft");
  console.log("⏳ Claim Period Left:", claimPeriodLeft);

  const withdrawalTimeLeft = useContractReader(readContracts, "Staker", "withdrawalTimeLeft");
  console.log("⏳ Withdrawal Time Left:", withdrawalTimeLeft);


  // ** Listen for when the contract has been 'completed'
  const complete = useContractReader(readContracts, "ExampleExternalContract", "completed");
  console.log("✅ complete:", complete);

  const exampleExternalContractBalance = useBalance(
    localProvider,
    readContracts && readContracts.ExampleExternalContract ? readContracts.ExampleExternalContract.address : null,
  );
  if (DEBUG) console.log(" exampleExternalContractBalance", exampleExternalContractBalance);

  let completeDisplay = "";
  if (complete) {
    completeDisplay = (
      <div style={{padding: 64, backgroundColor: "#eeffef", fontWeight: "bold", color: "rgba(0, 0, 0, 0.85)" }} >
        --  Staking App Fund Repatriation Executed  --
        <Balance balance={exampleExternalContractBalance} fontSize={32} /> ETH locked!
      </div>
    );
  }

  /*
  const addressFromENS = useResolveName(mainnetProvider, "austingriffith.eth");
  console.log(" Resolved austingriffith.eth as:", addressFromENS)
  */

  //
  //  DEBUG ‍
  //
  useEffect(() => {
    if (
      DEBUG &&
      mainnetProvider &&
      address &&
      selectedChainId &&
      yourLocalBalance &&
      yourMainnetBalance &&
      readContracts &&
      writeContracts &&
      mainnetContracts
    ) {
      console.log("_____________________________________  scaffold-eth _____________________________________");
      console.log(" mainnetProvider", mainnetProvider);
      console.log(" localChainId", localChainId);
      console.log("‍ selected address:", address);
      console.log("‍♂️ selectedChainId:", selectedChainId);
      console.log(" yourLocalBalance", yourLocalBalance ? ethers.utils.formatEther(yourLocalBalance) : "...");
      console.log(" yourMainnetBalance", yourMainnetBalance ? ethers.utils.formatEther(yourMainnetBalance) : "...");
      console.log(" readContracts", readContracts);
      console.log(" DAI contract on mainnet:", mainnetContracts);
      console.log(" yourMainnetDAIBalance", myMainnetDAIBalance);
      console.log(" writeContracts", writeContracts);
    }
  }, [
    mainnetProvider,
    address,
    selectedChainId,
    yourLocalBalance,
    yourMainnetBalance,
    readContracts,
    writeContracts,
    mainnetContracts,
  ]);

  let networkDisplay = "";
  if (NETWORKCHECK && localChainId && selectedChainId && localChainId !== selectedChainId) {
    const networkSelected = NETWORK(selectedChainId);
    const networkLocal = NETWORK(localChainId);
    if (selectedChainId === 1337 && localChainId === 31337) {
      networkDisplay = (
        <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
          <Alert
            message="⚠️ Wrong Network ID"
            description={
              <div>
                You have <b>chain id 1337</b> for localhost and you need to change it to <b>31337</b> to work with
                HardHat.
                <div>(MetaMask -> Settings -> Networks -> Chain ID -> 31337)</div>
              </div>
            }
            type="error"
            closable={false}
          />
        </div>
      );
    } else {
      networkDisplay = (
        <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
          <Alert
            message="⚠️ Wrong Network"
            description={
              <div>
                You have <b>{networkSelected && networkSelected.name}</b> selected and you need to be on{" "}
                <Button
                  onClick={async () => {
                    const ethereum = window.ethereum;
                    const data = [
                      {
                        chainId: "0x" + targetNetwork.chainId.toString(16),
                        chainName: targetNetwork.name,
                        nativeCurrency: targetNetwork.nativeCurrency,
                        rpcUrls: [targetNetwork.rpcUrl],
                        blockExplorerUrls: [targetNetwork.blockExplorer],
                      },
                    ];
                    console.log("data", data);

                    let switchTx;
                    // https://docs.metamask.io/guide/rpc-api.html#other-rpc-methods
                    try {
                      switchTx = await ethereum.request({
                        method: "wallet_switchEthereumChain",
                        params: [{ chainId: data[0].chainId }],
                      });
                    } catch (switchError) {
                      // not checking specific error code, because maybe we're not using MetaMask
                      try {
                        switchTx = await ethereum.request({
                          method: "wallet_addEthereumChain",
                          params: data,
                        });
                      } catch (addError) {
                        // handle "add" error
                      }
                    }

                    if (switchTx) {
                      console.log(switchTx);
                    }
                  }}
                >
                  <b>{networkLocal && networkLocal.name}</b>
                </Button>
              </div>
            }
            type="error"
            closable={false}
          />
        </div>
      );
    }
  } else {
    networkDisplay = (
      <div style={{ zIndex: -1, position: "absolute", right: 154, top: 28, padding: 16, color: targetNetwork.color }}>
        {targetNetwork.name}
      </div>
    );
  }

  const loadWeb3Modal = useCallback(async () => {
    const provider = await web3Modal.connect();
    setInjectedProvider(new ethers.providers.Web3Provider(provider));

    provider.on("chainChanged", chainId => {
      console.log(`chain changed to ${chainId}! updating providers`);
      setInjectedProvider(new ethers.providers.Web3Provider(provider));
    });

    provider.on("accountsChanged", () => {
      console.log(`account changed!`);
      setInjectedProvider(new ethers.providers.Web3Provider(provider));
    });

    // Subscribe to session disconnection
    provider.on("disconnect", (code, reason) => {
      console.log(code, reason);
      logoutOfWeb3Modal();
    });
  }, [setInjectedProvider]);

  useEffect(() => {
    if (web3Modal.cachedProvider) {
      loadWeb3Modal();
    }
  }, [loadWeb3Modal]);

  const [route, setRoute] = useState();
  useEffect(() => {
    setRoute(window.location.pathname);
  }, [setRoute]);

  let faucetHint = "";
  const faucetAvailable = localProvider && localProvider.connection && targetNetwork.name.indexOf("local") !== -1;

  const [faucetClicked, setFaucetClicked] = useState(false);
  if (
    !faucetClicked &&
    localProvider &&
    localProvider._network &&
    localProvider._network.chainId === 31337 &&
    yourLocalBalance &&
    ethers.utils.formatEther(yourLocalBalance) <= 0
  ) {
    faucetHint = (
      <div style={{ padding: 16 }}>
        <Button
          type="primary"
          onClick={() => {
            faucetTx({
              to: address,
              value: ethers.utils.parseEther("0.01"),
            });
            setFaucetClicked(true);
          }}
        >
           Grab funds from the faucet ⛽️
        </Button>
      </div>
    );
  }

  return (
    <div className="App">
      {/* ✏️ Edit the header and change the title to your project name */}
      <Header />
      {networkDisplay}
      <BrowserRouter>
        <Menu style={{ textAlign: "center" }} selectedKeys={[route]} mode="horizontal">
          <Menu.Item key="/">
            <Link
              onClick={() => {
                setRoute("/");
              }}
              to="/"
            >
              Staker UI
            </Link>
          </Menu.Item>
          <Menu.Item key="/contracts">
            <Link
              onClick={() => {
                setRoute("/contracts");
              }}
              to="/contracts"
            >
              Debug Contracts
            </Link>
          </Menu.Item>
        </Menu>

        <Switch>
          <Route exact path="/">
            {completeDisplay}

            <div style={{ padding: 8, marginTop: 16 }}>
              <div>Staker Contract:</div>
              <Address value={readContracts && readContracts.Staker && readContracts.Staker.address} />
            </div>

            <Divider />

            <div style={{ padding: 8, marginTop: 16 }}>
              <div>Reward Rate Per Second:</div>
              <Balance balance={rewardRatePerSecond} fontSize={64} /> ETH
            </div>

            <Divider />

            <div style={{ padding: 8, marginTop: 16, fontWeight: "bold" }}>
              <div>Claim Period Left:</div>
              {claimPeriodLeft && humanizeDuration(claimPeriodLeft.toNumber() * 1000)}
            </div>

            <div style={{ padding: 8, marginTop: 16, fontWeight: "bold"}}>
              <div>Withdrawal Period Left:</div>
              {withdrawalTimeLeft && humanizeDuration(withdrawalTimeLeft.toNumber() * 1000)}
            </div>

            <Divider />

            <div style={{ padding: 8, fontWeight: "bold"}}>
              <div>Total Available ETH in Contract:</div>
              <Balance balance={stakerContractBalance} fontSize={64} />
            </div>

            <Divider />

            <div style={{ padding: 8,fontWeight: "bold" }}>
              <div>ETH Locked  in Staker Contract:</div>
              <Balance balance={balanceStaked} fontSize={64} />
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={"default"}
                onClick={() => {
                  tx(writeContracts.Staker.execute());
                }}
              >
                 Execute!
              </Button>
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={"default"}
                onClick={() => {
                  tx(writeContracts.Staker.withdraw());
                }}
              >
                 Withdraw
              </Button>
            </div>

            <div style={{ padding: 8 }}>
              <Button
                type={balanceStaked ? "success" : "primary"}
                onClick={() => {
                  tx(writeContracts.Staker.stake({ value: ethers.utils.parseEther("0.5") }));
                }}
              >
                 Stake 0.5 ether!
              </Button>
            </div>

            {/*
                 this scaffolding is full of commonly used components
                this <Contract/> component will automatically parse your ABI
                and give you a form to interact with it locally
            */}

            {/* uncomment for a second contract:
            <Contract
              name="SecondContract"
              signer={userProvider.getSigner()}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
            */}
          </Route>
          <Route path="/contracts">
            <Contract
              name="Staker"
              signer={userSigner}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
            <Contract
              name="ExampleExternalContract"
              signer={userSigner}
              provider={localProvider}
              address={address}
              blockExplorer={blockExplorer}
              contractConfig={contractConfig}
            />
          </Route>
        </Switch>
      </BrowserRouter>

      <ThemeSwitch />

      {/* ‍ Your account is in the top right with a wallet at connect options */}
      <div style={{ position: "fixed", textAlign: "right", right: 0, top: 0, padding: 10 }}>
        <Account
          address={address}
          localProvider={localProvider}
          userSigner={userSigner}
          mainnetProvider={mainnetProvider}
          price={price}
          web3Modal={web3Modal}
          loadWeb3Modal={loadWeb3Modal}
          logoutOfWeb3Modal={logoutOfWeb3Modal}
          blockExplorer={blockExplorer}
        />
        {faucetHint}
      </div>

      <div style={{ marginTop: 32, opacity: 0.5 }}>
        {/* Add your address here */}
        Created by <Address value={"Your...address"} ensProvider={mainnetProvider} fontSize={16} />
      </div>

      <div style={{ marginTop: 32, opacity: 0.5 }}>
        <a target="_blank" style={{ padding: 32, color: "#000" }} href="https://github.com/scaffold-eth/scaffold-eth">
           Fork me!
        </a>
      </div>

      {/*  Extra UI like gas price, eth price, faucet, and support: */}
      <div style={{ position: "fixed", textAlign: "left", left: 0, bottom: 20, padding: 10 }}>
        <Row align="middle" gutter={[4, 4]}>
          <Col span={8}>
            <Ramp price={price} address={address} networks={NETWORKS} />
          </Col>

          <Col span={8} style={{ textAlign: "center", opacity: 0.8 }}>
            <GasGauge gasPrice={gasPrice} />
          </Col>
          <Col span={8} style={{ textAlign: "center", opacity: 1 }}>
            <Button
              onClick={() => {
                window.open("https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA");
              }}
              size="large"
              shape="round"
            >
              <span style={{ marginRight: 8 }} role="img" aria-label="support">
                
              </span>
              Support
            </Button>
          </Col>
        </Row>

        <Row align="middle" gutter={[4, 4]}>
          <Col span={24}>
            {
              /*  if the local provider has a signer, let's show the faucet:  */
              faucetAvailable ? (
                <Faucet localProvider={localProvider} price={price} ensProvider={mainnetProvider} />
              ) : (
                ""
              )
            }
          </Col>
        </Row>
      </div>
    </div>
  );
}

export default App;

真棒!我们已经做了很多工作。

在开发者环境、Solidity和前端代码方面,我们已经一起完成了很多新的组件。

验证您的dApp的功能是否符合预期!

  1. 1. dApp是否具有单次使用押金的功能?
  2. 2. 提款/资金返还条件是否得到尊重?继续点击yarn deploy --reset几次,检查每个时间窗口。

挑战时间!

好了,现在到了最精彩的部分。我将留给你一些扩展挑战,让你自己去尝试,看看你是否完全理解了你在这里学到的东西!

  1. 1. 更新Staker.sol合约中的利息机制,这样你就可以根据存款和提款之间的区块获得 "非线性 "的ETH数额

我建议实现一个基本的指数函数!

  1. 1. 允许用户向智能合约存入任意数量的ETH,而不仅仅是0.5ETH。
  2. 2. 不要使用vanilla ExampleExternalContract合约,在Staker.sol中实现一个函数,允许你取回锁定在ExampleExternalContract中的ETH,并将其重新存入Staker合约中。
  • • 请确保只有 "白名单 "中的一个地址可以调用这个新函数,以控制其使用。
  • • 确保你创建了逻辑/删除了现有的代码,以确保用户能够一次又一次地与Staker合同互动 我们希望能够反复地从Staker -> ExampleExternalContract进行ping-pong。

引用链接

[1] Scaffold-eth: http://scaffoldeth.io/
[2] SpeedRunEthereum:
https://speedrunethereum.com/
[3] Challenge #1:
https://speedrunethereum.com/challenge/decentralized-staking
[4] SpeedRunEthereum:
https://speedrunethereum.com/
[5] Challenge #1:
https://speedrunethereum.com/challenge/decentralized-staking
[6] 准则:
https://docs.alchemy.com/docs/how-to-build-a-staking-dapp#project-parameters
[7] 公共变量部分:
https://docs.alchemy.com/docs/how-to-build-a-staking-dapp#public-variables
[8] Solidity By Example:
https://solidity-by-example.org/function-modifier
[9] 链接573附近的代码块:
https://github.com/scaffold-eth/scaffold-eth/blob/challenge-1-decentralized-staking/packages/react-app/src/App.jsx#L573

到前端技术,不少朋友一定会感到有些陌生。但其实,前端,你每天都在接触。

你正在使用的APP,你正在浏览的网页,这些你能看到的界面,都属于前端。

而前端最重要的三大技术,HTML,CSS,JavaScript,则是每一个前端开发者必须具备的技能。

掌握这些技能,你可以快速地做出一个酷炫的APP界面或者一个简单大方的网站页面。因此,就让我们一起来快速学习一下这三门技术吧。



以下内容节选自实验楼训练营课程《Vue.js 和 Node.js 构建内容发布系统》。

实验介绍

本实验主要介绍一下前端的基础知识,对比认识一下各个框架的代码编写方式,并介绍我们本次技术选型的主要思路。对于前端三大技术 HTML、CSS、JavaScript,简单的介绍了基本情况和常用语法。中间介绍了现代框架的一些情况,并通过实际的案例,用代码直观的认识一下各种框架的实现方式。最后分析一下项目的技术选型。

知识点

  • HTML、CSS、JavaScript 快速概览
  • 前端框架概览和选型
  • 后端选型
  • 数据库选型
  • Web 服务器选型

前端技术简介

本节我们简单介绍一下前端最基础 HTML, CSS, JavaScript 三驾马车。虽然本课程预设的读者为零基础开发者,但是前端开发一定会这三种技术的运用有要求。建议抽空学习 《 Web 前端工程师路径》 中的阶段 1 甚至阶段 2。这里仅做语法介绍和基本使用的概览。

在此之前先认识一下实验环境。实验环境和 VS Code 使用体验基本一致。你可以启动一个终端,并在其中输入 Linux 命令。

后面的命令无特殊说明的都是在此终端命令行中输入。大多数命令可以多开终端窗口分别执行。

那么下面我们就快速的了解一下。

HTML

HTML 全称超文本标记语言,几乎是从万维网和浏览器产生伊始就存在的。主要用于结构化信息来方便浏览器展示。

以标签对作为主要特征,如<p>这是一个段落</p>。这些标签会被浏览器解析成不同的模块,比如 p 标签就是一个段落,img 标签就是一个图片,a 标签就是一个超链接,标签名不区分大小写。

立刻就来尝试一下吧。首先通过命令行新建一个 demo 目录:

mkdir demo

然后命令行进入 demo 目录:

cd ./demo

新建一个 hello.html 文件,可以在实验环境左边的浏览框内在 demo 上右键选择 New File 然后命名为 hello.html;或者也可在命令行终端输入 touch hello.html,同样是新建文件。

在其中输入以下内容:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>标题</title>
  </head>
  <body>
    正文
  </body>
</html>

然后右击文件选择 Open With → Preview。

看到了吗?其实我们就是新建了一个 .html 后缀的文本文件,然后浏览器就可以将其中的内容展示出来。你也可以在自己的桌面上新建一个,然后使用浏览器打开看看效果。

这里嵌套代码的缩进格式是为了美观和可读性,并无严格要求。

head 标签中是一些暂时无需用到的头部信息,渲染的主体是 body 标签。下面我们修改 body 标签里面的内容,填入一些常用标签来直观感受一下。

<body>
  <h1>页面标题</h1>
  <div>一个块容器</div>
  <div>又一个块容器</div>
  <p>这里是段落了,间距变大</p>
  <div>一个块容器</div>
  <div>
    <div>
      多层嵌套:
      <div>内部第一个</div>
      <div>内部第二个</div>
    </div>
  </div>
</body>

保存之后切换到浏览标签看一看,有没有感觉被忽悠了?嵌套没嵌套根本没体现出来,就像 Word 里排了一下版,按了几个回车。

因为我们没有对显示样式进行修改,那是 CSS 的事。HTML 主要管内容的组织结构。

这里有一点针对学习的小建议,本课程中给到的全部代码请手动输入,忘记复制和粘贴快捷键。

而且最好不要机械的一个字符一个字符照着抄,尽量看过一行或一小段代码之后,靠短暂的印象去输出,别怕出错,只有过脑子并输出实践,才是最快掌握一项技能的捷径。

以上两句话是本课程中最有价值内容之一。

下面我们接着修改刚才的代码,再给 body 中添加几个常用标签。每次修改和保存之后记得到预览页看看样式的变化。

<h4>4 级标题</h4>
<ul>
  <li>
    HTML
  </li>
  <li>
    CSS
  </li>
  <li>
    JavaScript
  </li>
</ul>
<div>
  <a href="https://www.shiyanlou.com" target="_blank"
    >点击超链接跳转至实验楼首页</a
  >
</div>
<div>
  <img
    src="https://static.shiyanlou.com/frontend/dist/img/9f43b00.svg"
    alt=""
    width="200"
  />
</div>

最后的链接标签 a 和图片标签 img 出现了标签属性,属性为 attr="value" 格式,可以给标签增加更丰富的信息。

同时 img 标签还是一个单标签,不需要在后面添加 </img> 配合使用。

至此对 HTML 的简要介绍告一段落。

互联网上看到的各种五彩缤纷网页都是由这些 HTML 组成的,但是为什么我们写的这么难看?下一节我们就要学习一下如何用 CSS 美化页面。

CSS

CSS 全称层叠样式表,是专门用来修饰 HTML 样式的一种语言。我们修改一下上节的 hello.html 文件来直观感受一下。


内部代码块引入

在 head 标签内部增加以下 style 代码块:

<head>
  <meta charset="UTF-8" />
  <title>标题</title>
  <style type="text/css">
    div {
      border: 1px solid blue;
      padding: 2px;
      margin: 10px;
    }
  </style>
</head>

这是再切换到预览页,发现没那么平铺直叙了。

这就是 CSS 的第一种引入方式,HTML 内置代码块。

大括号外面的 div 是标签选择器,这里选中了本页面中的所有 div 元素。大括号里面是属性名与赋值,属性名都是固定的关键字,并已规定好了值的类型和可选范围。

读代码也就大概知道了,我们将 div 的边框设置为 1 像素宽、固体(单线型)、蓝色,填充(内边距)2 像素,边缘空白(外边距)10 像素。现在可以练习调整一下各个数字,预览看看发生了什么?

再说点题外话,懂一些英文对程序员来说非常必要,除了可以凭感觉就读懂没学过的代码,还可以在面向 Google 编程、面向 Stack Overflow 编程、面向 Github Issues 编程时游刃有余。


外部文件引入

然后我们再试一下外部文件引入,在 hello.html 的同级目录新建 hello.css,输入以下内容保存:

div {
  color: green;
  border: 2px dotted red;
}

然后修改 hello.html,在 style 标签后面增加一行 link 标签,添加引入类型和地址:

<style type="text/css">
  div {
    border: 1px solid blue;
    padding: 2px;
    margin: 10px;
  }
</style>
<link rel="stylesheet" href="hello.css" />

预览看看,文字颜色变为绿色,边框的样式也被更新为 2 像素宽、点线型红色。

同样是 div 选择器,为什么边框的样式被覆盖了呢?注意 CSS 在同样条件下会后面代码覆盖前面,可以尝试交换 link 标签和 style 标签块的顺序看看。


行间样式

最后一种叫行间样式,这个结构更简单。修改 hello.html 中的 <div>内部第一个</div> 为

<div style="margin: 60px 0 10px 30px ;color:purple;">内部第一个</div>

样式覆盖前两种方式了,因为行间样式的优先级较高。这里涉及到选择器权重,先给一个简单公式了解一下。

!important > 行间样式 > ID > class | 伪类 | 属性选择 > 标签 > 继承 | 通配符。

多个选择器作用时权重相加。这里算 CSS 里有点复杂的部分,暂时不展开。

这里还有个小知识点是内外边距 margin 和 padding 接受的完整的值是四个,顺序固定为“上 右 下 左”。如果省略参数则从末尾计算对向合并。比如:

  • margin:40px 20px 50px; 三个参数时,左右同为 20px。
  • margin:40px 20px; 两个参数时 上下同为 40px, 左右同为 20px。
  • margin:40px; 一个参数时呢?请自行尝试理解。

CSS 先讲这么多,虽然没有把我们的页面变多好看,但最起码知道努力的方向了。

JavaScript

制作 JavaScript 的快速入门简直非常伤脑筋。比起前两种技术 HTML 和 CSS,这是货真价实的编程语言了。

也是我们后面要用到的 Vue.js 和 Node.js 中的根基,一下子又很难讲很多,所以还是希望同学们能重视起来系统学习一下,最起码读到后面的代码时不至于陷入“这是啥这又是啥”的窘境。

来段代码直观认知一下,还是先内部代码块引入。

在 hello.html 的 head 标签内增加一个代码块:

    <link rel="stylesheet" href="./hello.css">
    <script>
      let message = "字符串提示";
      function showMSG(msg) {
        alert(msg);
      }
    </script>

修改 hello.html 的 h1 标签为:

<h1 onclick="showMSG(message)">页面标题</h1>

保存预览,点击“页面标题”,会弹出提示框。

JavaScript 代码加载之后就会执行,不存在编译阶段。行末的分号绝大多数时候可以省略。

我们先定义了一个变量 message,并赋值为“字符串提示”。定义变量关键字原是 var,ES6 新增关键字 let 有更清晰的作用域,可替代使用。

学习 JavaScript 经常会碰到 ES6、ES7 之类的名词,实际上是 ECMAScript 标准的版本号的意思。可以简单理解为新版标准为 JavaScript 添加特定新特性。

然后我们定义了一个函数 showMSG,并添加一个形参 msg。在函数体内部调用浏览器弹框方法,显示 msg 的值。function 是定义函数的关键字,暂时先把它当做一个功能封闭的盒子,当函数调用时,执行函数体内的代码。

调用部分是先给 h1 标签添加了 onclick 点击事件,被点击时触发 showMSG(message),也就是把 message 传给了 msg。

之后再试一下调用外部 js 文件,新建 demo.js 文件,写入下面内容并保存。

message = "修改一下字符串";

然后修改 hello.html 文件,在 script 代码块后面增加一行:

<script src="./demo.js"></script>

这次保存预览,点击“页面标题”,可以看到弹窗的文字变了。这个演示了 script 代码块在页面可以同时存在多个,也是顺序调用,而且互相之间可以直接访问。文件命名也没有要求,希望不会逼死强迫症。

JavaScript 就是为什么网页可以做那么多交互的源头了。掌握起来任重道远。

以上内容节选自实验楼训练营课程《Vue.js 和 Node.js 构建内容发布系统》。


这三门前端技术先了解到这里,想要更深入学习如何使用前端技术构建内容发布系统,比如做个高逼格的博客,搭建一个交流社区,或者为企业制作官网等,可以访问实验楼官网搜索《Vue.js 和 Node.js 构建内容发布系统》这门课。

课程会提供完整的虚拟机环境,手把手教大家如何从头构建实现一个前后端分离的内容发布系统,包括了前端页面、后端服务、数据库等。