vue 是一套在前端開發中廣泛采用的用於構建用戶界面的漸進式JavaScript框架。vue 通過響應的數據綁定和組合的視圖組件讓界面開發變得非常的簡單。這邊文章來看看如何使用Vue開發以太坊DApp。
Vue 簡介
Vue 除了是JavaScript框架,還提供了一個配套的命令行工具 Vue CLI,通常稱之為腳手架工具,用來進行項目管理,比如快速開始零配置原型開發,安裝插件庫等。
Vue CLI 可以通過以下命令安裝:
> npm install -g @vue/cli
運行以下命令來創建一個名為crowdfunding的新項目:
> vue create crowdfunding
命令會生成一個項目目錄,並安裝好相應的依賴庫,生成的主要文件有:
├── package.json ├── public │ ├── index.html └── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── CrowdFund.vue │ └── HelloWorld.vue └── main.js
簡單介紹一下Vue 生成的文件,更多的使用介紹,可參考 vue.js 文檔[6]。
index.html 是入口文件,里面定義了一個div標簽:
<div id="app"></div>
在main.js 中,會把APP.vue的組件的內容渲染到id 為app的div標簽內:
new Vue({ render: h => h(App), }).$mount('#app')
APP.vue 組件又引用了Hello.vue 組件,創建完成后進入目錄,就可以運行項目,命令如下:
> cd crowdfunding
> npm run serve (或 yarn serve)
此時會在8080端口下,啟動一個Web服務,瀏覽器輸入URL: http://localhost:8080 ,會打開前端頁面。
DApp 需求分析
在剛剛創建的工程下來完成眾籌DApp,先分析下需求,假設我准備出版一本區塊練技術書籍,但是不確定有多少人願意購買這本書,於是我發起了一個眾籌, 如果在一個月內,能籌集到10個ETH,我就進行寫作,並給參與的讀者每人贈送一本書,如果未能籌到足夠的資金,參與的讀者贖回之前投入的資金。
同時,為了讓讀者積極參與,初始時,參與眾籌的價格非常低(0.02 ETH),每籌集滿1個ETH時,價格上漲0.002ETH。
歸納出合約三個對外動作(函數):
匯款進合約,可通過實現合約的回退函數來實現。讀者贖回匯款,這個函數僅僅在眾籌未達標之后,由讀者本人調用生效。創作者提取資金,這個函數需要在眾籌達標之后,由創作者調用。
除此之外,還需要一個狀態變量來保存以下狀態:
用戶眾籌的金額,使用一個mapping 來保存。當前眾籌的價格,以及一個相應的內部函數逐步上漲價格。價格可以使用一個uint來保存。合約眾籌的截止時間,用uint來保存, 在合約創建時往后追加30天,在構造函數中完成。記錄合約眾籌的收益者,即創作者,在合約創建時在構造函數中進行賦值,合約創建者就是創作者。再加入眾籌狀態,如果眾籌停止阻止用戶在參與。創作者提取資金時及時關閉眾籌。
實現眾籌合約
DApp的開發,我們依然可以使用Truffle框架,先進入crowdfunding目錄,使用 truffle init進行一下 truffle 項目初始化:
> truffle init
初始化完成后, 會在當前目錄下生成 truffle-config.js 配置文件及 contracts migrations 文件夾等內容,之后,就可以在項目下使用 truffle compile 來編譯合約及 truffle migrate 來部署合約。
在contracts 下創建一個合約文件Crowdfunding.sol:
pragma solidity >=0.4.21 <0.7.0; contract Crowdfunding { // 創作者 address public author; // 參與金額 mapping(address => uint) public joined; // 眾籌目標 uint constant Target = 10 ether; // 眾籌截止時間 uint public endTime; // 記錄當前眾籌價格 uint public price = 0.02 ether ; // 作者提取資金之后,關閉眾籌 bool public closed = false; // 部署合約時調用,初始化作者以及眾籌結束時間 constructor() public { author = msg.sender; endTime = now + 30 days; } // 更新價格,這是一個內部函數 function updatePrice() internal { uint rise = address(this).balance / 1 ether * 0.002 ether; price = 0.02 ether + rise; } // 用戶向合約轉賬時 觸發的回調函數 function () external payable { require(now < endTime && !closed , "眾籌已結束"); require(joined[msg.sender] == 0 , "你已經參與過眾籌"); require (msg.value >= price, "出價太低了"); joined[msg.sender] = msg.value; updatePrice(); } // 作者提取資金 function withdrawFund() external { require(msg.sender == author, "你不是作者"); require(address(this).balance >= Target, "未達到眾籌目標"); closed = true; msg.sender.transfer(address(this).balance); } // 讀者贖回資金 function withdraw() external { require(now > endTime, "還未到眾籌結束時間"); require(!closed, "眾籌達標,眾籌資金已提取"); require(Target > address(this).balance, "眾籌達標,你沒法提取資金"); msg.sender.transfer(joined[msg.sender]); } }
代碼的介紹,我以注釋的形式加入在代碼中,除此之外,在合約代碼中,使用到了Solidity語言中的一些知識點:
ether days now require address.transfer(value)
有關Solidity 語言特性,大家可參考深入淺出區塊鏈社區翻譯的Solidity中文文檔
合約部署
在migrations下創建一個部署腳本 2_crowfunding.js , 和寵物商店DApp類似,內容如下:
const crowd = artifacts.require("Crowdfunding"); module.exports = function(deployer) { deployer.deploy(crowd); };
在 truffe-config.js 配置要部署的網絡,同時確保對應的網絡節點程序是開啟狀態,方法參考寵物商店DApp或鏈上筆記本投票合約案例中一樣,然后就可以 truffle migrate 進行部署。
眾籌Web界面實現
默認會有一個HelloWorld.vue, 新寫一個組件 CrowdFund.vue ,把App.vue中對HelloWorld.vue的引用替換掉。
App.vue 修改為:
<template> <div id="app"> <CrowdFund/> </div> </template> <script> import CrowdFund from './components/CrowdFund.vue' export default { name: 'app', components: { CrowdFund } } </script>
利用CrowdFund.vue來眾籌界面,眾籌界面需要顯示以下幾個部分:
當前眾籌到金額。眾籌的截止時間。當前眾籌的價格,參與眾籌按鈕。如果是已經參與,顯示其參與的價格以及贖回按鈕。如果是創作者,顯示一個提取資金按鈕。因為Vue 具有很好的數據綁定及條件渲染特性,因此前端寫起來會更簡單,只需要把相應的數據用變量替代,代碼如下:
<template> <div class="content"> <h3> 新書眾籌</h3> <span>以最低的價格獲取我的新書 </span> <!-- 眾籌的總體狀態 --> <div class="status"> <div v-if="!closed">已眾籌資金:<b>{{ total }} ETH </b></div> <div v-if="closed"> 眾籌已完成 </div> <div>眾籌截止時間:{{ endDate }}</div> </div> <!-- 當讀者參與過,顯示如下div --> <div v-if="joined" class="card-bkg"> <div class="award-des"> <span> 參與價格 </span> <b> {{ joinPrice }} ETH </b> </div> <button :disabled="closed" @click="withdraw">贖回</button> </div> <!-- 當讀者未參與,顯示如下div --> <div v-if="!joined" class="card-bkg"> <div class="award-des"> <span> 當前眾籌價格 </span> <b> {{ price }} ETH </b> </div> <button :disabled="closed" @click="join">參與眾籌</button> </div> <!-- 如果是創作者,顯示 --> <div v-if="isAuthor"> <button :disabled="closed" @click="withdrawFund"> 提取資金</button> </div> </div> </template>
代碼中使用Vue的特性包含:
使用 v-if 進行條件渲染,例如 v-if="joined" 表示當joined變量為true 時,才渲染標簽。使用 進行數據綁定, 例如: <b> ETH </b> , price會用其真實的值進行渲染,並且當price變量的值更新時,標簽會自動更新。使用 @click 指令來監聽事件, @click 實際上是 v-on:click 的縮寫,例如: @click="join" 表示當標簽點擊時,會調用join函數。使用 :disabled 綁定一個屬性,這實際是 v-bind:disabled , 屬性的值來源於一個變量。
與眾籌合約交互
現在來編寫JavaScript邏輯部分,前端界面與合約進行交互時,需要使用到 truffle-contract及 web3 ,因為Vue 工程本身也是通過NPM進行包管理,因此可以直接通過npm進行安裝,命令如下:
npm install --save truffle-contract web3
先把JavaScript邏輯的主體框架代碼編寫出來:
<script> export default { name: 'CrowdFund', // 定義上面HTML模板中使用的變量 data() { return { price: null, total: 0, closed: true, joinPrice: null, joined: false, endDate: "null", isAuthor: true, } }, // 當前Vue組件被創建時回調的hook 函數 async created() { // 初始化 web3及賬號 await this.initWeb3Account() // 初始化合約實例 await this.initContract() // 獲取合約的狀態信息 await this.getCrowdInfo() }, methods: { async initWeb3Account() {} async initContract() {} async getCrowdInfo() {} } } </script>
以上代碼通過data() 定義好了上面HTML模板中使用的變量, 當Vue組件被創建時通過回調的created()函數, 來進行初始化工作(這里使用了async/await 來簡化異步調用), 在created()函數中調用了三個函數:
initWeb3Account()initContract()getCrowdInfo()依次來進行實現:
initWeb3Account() 用來完成 web3及賬號初始化,代碼和投票案例基本類似,代碼如下:
import Web3 from "web3"; async initWeb3Account() { if (window.ethereum) { this.provider = window.ethereum; try { await window.ethereum.enable(); } catch (error) { // console.log("User denied account access"); } } else if (window.web3) { this.provider = window.web3.currentProvider; } else { this.provider = new Web3.providers.HttpProvider("http://127.0.0.1:7545"); } this.web3 = new Web3(this.provider); this.web3.eth.getAccounts().then(accs => { this.account = accs[0] }) },
通過這段代碼完成了 this.provider this.web3 this.account 三個變量的賦值,在后面的代碼中會使用到。
initContract() 初始化合約實例:
import contract from "truffle-contract"; import crowd from '../../build/contracts/Crowdfunding.json'; async initContract() { const crowdContract = contract(crowd) crowdContract.setProvider(this.provider) this.crowdFund = await crowdContract.deployed() },
this.crowdFund 變量就是部署的眾籌合約對應得JavaScript 中的實例, 下面就可以通過this.crowdFund來調用合約的函數,獲取相關變量的值,也就是實現 getCrowdInfo 函數:
async getCrowdInfo() { // 獲取合約的余額 this.web3.eth.getBalance(this.crowdFund.address).then( r => { this.total = this.web3.utils.fromWei(r) } ) // 獲取讀者的參與金額 this.crowdFund.joined(this.account).then( r => { if (r > 0) { this.joined = true this.joinPrice = this.web3.utils.fromWei(r) } } ) // 獲取合約的關閉狀態 this.crowdFund.closed().then( r => this.closed = r ) // 獲取當前的眾籌價格 this.crowdFund.price().then( r => this.price = this.web3.utils.fromWei(r) ) // 獲取眾籌截止時間 this.crowdFund.endTime().then(r => { var endTime = new Date(r * 1000) // 把時間戳轉化為本地時間 this.endDate = endTime.toLocaleDateString().replace(/\//g, "-") + " " + endTime.toTimeString().substr(0, 8); }) // 獲取眾籌創作者地址 this.crowdFund.author().then(r => { if (this.account == r) { this.isAuthor = true } else { this.isAuthor = false } }) }
對代碼中使用到的幾個技術點,進行下解釋:
合約實例 this.crowdFund 調用的函數 joined() closed() price() 是由合約中是public 的狀態變量,自動生成相應的訪問器函數。可以回顧第6章。代碼中使用的 this.web3.eth.getBalance() 和 this.web3.utils.fromWei() 是Web3.js 中定義的函數,分別用來獲取余額及把單位wei轉化為ether。至此,完成DApp的狀態數據的獲取,接下來開始處理3個點擊動作(即html模板中@click觸發的函數):
讀者參與眾籌的join()函數;讀者贖回的withdraw()函數;創作者提取資金的withdrawFund()函數。join() 需要完成的實際上是由讀者賬號向眾籌合約賬號發起一筆轉賬,通過 web3.eth.sendTransaction 完成,代碼如下:
join() {
this.web3.eth.sendTransaction({ from: this.account, to: this.crowdFund.address, value: this.web3.utils.toWei(this.price) }).then(() => this.getCrowdInfo() ) }
讀者進行轉賬時,就會觸發合約的回退函數。
如果眾籌未達標,讀者可以點擊贖回,對應withdraw()函數實現如下:
withdraw() {
this.crowdFund.withdraw({ from: this.account }).then(() => { this.getCrowdInfo() }) },
如果眾籌達標,創作者提取資金withdrawFund()函數實現如下:
withdrawFund() {
this.crowdFund.withdrawFund({ from: this.account }).then(() => { this.getCrowdInfo() }) }
到這里眾籌案例就全部完成了,完整的代碼請大家 訂閱小專欄 。
電腦刺綉綉花廠 http://www.szhdn.com 廣州品牌設計公司https://www.houdianzi.com
DApp 運行
在項目的目錄下,輸入以下命令:
> npm run serve(或 yarn serve )
瀏覽器輸入 http://localhost:8080,如果是參與過眾籌(創作者賬號還會顯示一個”提取資金“),在運行DApp 時,要確保MetaMask鏈接的網絡和合約部署的網絡一致,這樣DApp才能正確的通過web3獲取到合約的數據。
DApp 發布
通過以下命令:
> npm run build(或 yarn build )
會再dist目錄下,構建出用戶發布的完整的前端代碼,其文件如下:
dist ├── css │ └── app.40b6ecb0.css ├── favicon.ico ├── index.html └── js ├── app.5b2f814c.js ├── app.5b2f814c.js.map ├── chunk-vendors.787aba35.js └── chunk-vendors.787aba35.js.map
index.html 就是DApp前端入口文件,把dist目錄下的所有文件拷貝到公網的服務器即可。