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質.押.合同都非常復雜,但我們將通過一個最基本的合同來學習關鍵概念。
我們將一起學習以下有關質.押.的構件。
讓我們從了解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.sol和App.jsx上工作。
接下來,你需要為以下三個命令建立三個獨立的終端。
啟動你的React前端。
yarn start
啟動你的Hardhat后端。
yarn chain
編譯、部署和發布你的package/contracts文件中的所有合同。
yarn deploy
每當你更新你的合同時,運行yarn deploy --reset來 "刷新 "Scaffold-Eth中的合同。
很好! 現在你應該可以在http://localhost:3000/,看到這個倉庫的UI前臺了。
雖然我知道你很想開始寫代碼,但還有一些細節需要處理
在我們的默認視圖中,我們有兩個標簽--Staker UI & Debug Contracts。
來吧,在你的前端頁面上來回切換,看看不同的功能。
Staker UI包含了所有我們將主要與之互動的前端組件。
如果你點擊所提供的按鈕,你會發現大多數按鈕還沒有完全連接起來,你會立即遇到錯誤。
看一下Staker UI。你會注意到它是如此的簡陋的! 這就是我們主要要改變的東西。
由于以太坊上的任何鏈上互動都需要testnet ETH,所以你需要本地testnet ETH來開始砍價。
首先,抓住你的本地主機錢包地址。
點擊右上角的 "復制 "按鈕。
接下來,前往左下角。你將能夠在這里訪問本地龍頭。
在為你的本地錢包充值后,你就可以與你的合約進行互動了
第二個標簽,Debug Contracts,是另一個前端顯示,它包含了Scaffold-Eth的一個超級大功能!在這個標簽中,你可以看到你的合同。
一旦你yarn deploy你的合約,并配置它正確地讀取合約數據,它將自動生成一個裸露的UI,允許你與你的合約功能互動。
例如,在下面的例子中,我們可以通過我們的智能合約讀取和寫入信息,只需放入參數并點擊 "發送"。有了Scaffold-Eth,我們不需要只使用CLI命令,而是有一個更直觀的原型設計方式。
如果你想在Debug Contracts 標簽頁中存儲和查看一個變量,請確保將該變量設置為public!
棒極了!
現在我們已經熟悉了Scaffold-Eth,我們可以更深入地研究代碼了。
看一下我們的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如何工作
雖然上面列出的質.押.參數可能看起來有點復雜,但許多現實生活中的質.押.dApps都有類似的基元,用戶有一個有限的存款和提款期。
而且,許多DApps將抑制 "非生產性 "資本,這些資本在質.押.期結束后只是閑置在那里。
有時,DeFi協議甚至可能在等待期結束后吸收未付的存款,這與我們在教程中所說的最后一個參數類似。
在我們的智能合約中,我們將需要兩個映射來幫助我們存儲一些數據。
特別是,我們需要一些東西來跟蹤。
我們可以通過以下方式實現這一目標。
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 :( ");
}
該函數的其余部分做了幾個重要步驟。
在這里,我們希望 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}();
}
其余的功能。
如果你到目前為止一直跟著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);
}
}
太棒了! 我們剛剛經歷了一堆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的平衡反應組件來幫助格式化!
為了構建截止日期視覺組件,我們使用了以下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來訪問前端的存儲變量值。
然而,我們實際上必須先從智能合約中讀取這些值本身。第一個代碼片斷處理了這個邏輯!
現在你已經看到了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] 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 前端工程師路徑》 中的階段 1 甚至階段 2。這里僅做語法介紹和基本使用的概覽。
在此之前先認識一下實驗環境。實驗環境和 VS Code 使用體驗基本一致。你可以啟動一個終端,并在其中輸入 Linux 命令。
后面的命令無特殊說明的都是在此終端命令行中輸入。大多數命令可以多開終端窗口分別執行。
那么下面我們就快速的了解一下。
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 全稱層疊樣式表,是專門用來修飾 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 接受的完整的值是四個,順序固定為“上 右 下 左”。如果省略參數則從末尾計算對向合并。比如:
CSS 先講這么多,雖然沒有把我們的頁面變多好看,但最起碼知道努力的方向了。
制作 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 構建內容發布系統》這門課。
課程會提供完整的虛擬機環境,手把手教大家如何從頭構建實現一個前后端分離的內容發布系統,包括了前端頁面、后端服務、數據庫等。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。