一、搭建主页面
主界面使用semantic-tab组件。
(1)在display目录下创建一个TabCenter.js文件,在该文件中定义一个Tab组件。代码如下所示:
import React from 'react'
import { Tab } from 'semantic-ui-react'
const panes = [
{ menuItem: '所有的', render: () => <Tab.Pane>1111111</Tab.Pane> },
{ menuItem: '我发起的', render: () => <Tab.Pane>22222222</Tab.Pane> },
{ menuItem: '我参与的', render: () => <Tab.Pane>333333333</Tab.Pane> },
]
const TabCenter = () => <Tab panes={panes} />
export default TabCenter
(2)在App.js文件显示主界面内容。
import React, {Component} from 'react';
import web3 from './utils/InitWeb3'
import TabCenter from './display/TabCenter'
class App extends Component {
constructor() {
super()
this.state = {
currentAccount: '',
}
}
async componentWillMount() {
// 获取合约的所有账户
let accounts = await web3.eth.getAccounts()
this.setState({
currentAccount: accounts[0],
})
}
render() {
return (
<div>
<h1>人人众筹</h1>
<p>当前账户:{this.state.currentAccount}</p>
<TabCenter/>
</div>
)
}
}
export default App;
(3)在index.js文件中导入css。
import 'semantic-ui-css/semantic.min.css'
运行程序,界面效果如下图所示:
(4)创建三个子页面,分别对应着Tab1、Tab2、Tab3的内容。
在display目录下创建AllFundingTab.js文件,代码如下:
import React, {Component} from 'react';
class AllFundingTab extends Component {
render() {
return (
<p>Tab11111.....</p>
)
}
}
export default AllFundingTab
在display目录下创建CreatorFundingTab.js文件,代码如下:
import React, {Component} from 'react';
class CreatorFundingTab extends Component {
render() {
return (
<p>Tab22222.....</p>
)
}
}
export default CreatorFundingTab
在display目录下创建SupportorFundingTab.js文件,代码如下:
import React, {Component} from 'react';
class SupportorFundingTab extends Component {
render() {
return (
<p>Tab33333.....</p>
)
}
}
export default SupportorFundingTab
(5)修改TabCenter.js文件,引入上面三个文件。
import AllFundingTab from "./AllFundingTab";
import SupportorFundingTab from "./SupportorFundingTab";
import CreatorFundingTab from "./CreatorFundingTab";
const panes = [
{ menuItem: '所有的', render: () => <Tab.Pane><AllFundingTab/></Tab.Pane> },
{ menuItem: '我发起的', render: () => <Tab.Pane><CreatorFundingTab/></Tab.Pane> },
{ menuItem: '我参与的', render: () => <Tab.Pane><SupportorFundingTab/></Tab.Pane> },
]
二、显示我发起的众筹
(1)修改CreatorFundingTab.js文件,引入web3实例。
import web3 from '../utils/InitWeb3'
(2)在componentWillMount方法中获取我发起的众筹,并设置到state中。
state = {
creatorFundings : [],
}
async componentWillMount() {
let accounts = await web3.eth.getAccounts()
let creatorFundings = await contractInstance.methods.getCreatorFunding().call({
from : accounts[0]
})
this.setState({
creatorFundings
})
}
(3)在render方法中从state获取数据。
render() {
return (
<p>{this.state.creatorFundings}</p>
)
}
运行程序,界面如下图所示:
三、获取合约详情
现在我们已经获取到了我发起过的合约。接下来我们就要根据合约地址获取合约的详细信息,然后再显示在界面上。
(1)修改instance.js文件,定义变量分别保存FundingFactory和Funding的ABI和合约地址。
// Factory合约的ABI
let factoryAbi = [ { "constant": true, "inputs": [], "name": "platformManager", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_name", "type": "string" }, { "name": "_targetMoney", "type": "uint256" }, { "name": "_supportMoney", "type": "uint256" }, { "name": "_duration", "type": "uint256" } ], "name": "createFunding", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getAllFundings", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getCreatorFunding", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getSupportorFunding", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" } ]
// Factory合约的地址
let factoryAddress = '0xa046025217a4550013a5720350a775d480bf1971'
// Funding合约的ABI
let fundingAbi = [ { "constant": false, "inputs": [ { "name": "i", "type": "uint256" } ], "name": "finalizeRequest", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getBalance", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "endTime", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getRequestCount", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "manager", "outputs": [ { "name": "", "type": "address" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "refund", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "getLeftTime", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_purpose", "type": "string" }, { "name": "_cost", "type": "uint256" }, { "name": "_seller", "type": "address" } ], "name": "createRequest", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "targetMoney", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getInvestorCount", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "projectName", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "supportMoney", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "getInvestors", "outputs": [ { "name": "", "type": "address[]" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "i", "type": "uint256" } ], "name": "getRequest", "outputs": [ { "name": "", "type": "string" }, { "name": "", "type": "uint256" }, { "name": "", "type": "address" }, { "name": "", "type": "uint256" }, { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "i", "type": "uint256" } ], "name": "approveRequest", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [], "name": "invest", "outputs": [ { "name": "", "type": "uint256" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "inputs": [ { "name": "_projectName", "type": "string" }, { "name": "_targetMoney", "type": "uint256" }, { "name": "_supportMoney", "type": "uint256" }, { "name": "_duration", "type": "uint256" }, { "name": "_creator", "type": "address" }, { "name": "_supportFunding", "type": "address" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor" } ]
创建FundingFactory和Funding的实例。因为一个众筹合约对应一个Funding实例,所以,这里需要创建多次Funding实例。而且创建Funding实例时候不需要传入合约地址,可以动态设置。
// 获取合约实例
let factoryContractInstance = new web3.eth.Contract(factoryAbi, factoryAddress)
// 获取Funding合约实例的时候,不需要指定合约地址。
let getFundingContractInstance = () => {
return new web3.eth.Contract(fundingAbi)
}
导出getFundingContractInstance。
// 导出合约实例
export {
factoryContractInstance,
getFundingContractInstance,
}
(2)在eth目录下新建interaction.js文件,在文件中定义一个方法,用于获取所有我发起过的合约详情。
import web3 from "../utils/InitWeb3";
import {factoryContractInstance, getFundingContractInstance} from "./instance";
// 获取我发起合约的详情
// 该函数返回一个Promise对象,该Promise对象包含了所有的合约详情
let getFundingDetail = async (funding) => {
// 遍历数组
let details = funding.map(async function(fundingAddress) {
let fundingContractInstance = getFundingContractInstance()
// 1.把Funding实例拿过来,并且为实例填充地址
fundingContractInstance.options.address = fundingAddress
// 2.调用方法获取Funding实例的详情
let manager = await fundingContractInstance.methods.manager().call()
let projectName = await fundingContractInstance.methods.projectName().call()
let targetMoney = await fundingContractInstance.methods.targetMoney().call()
let supportMoney = await fundingContractInstance.methods.supportMoney().call()
let leftTime = await fundingContractInstance.methods.getLeftTime().call()
// 3.创建结构体对象,封装合约信息
let detail = {manager, projectName, targetMoney, supportMoney, leftTime}
return detail
})
// 把多个Promise处理成一个Promise
let detailsPromise = Promise.all(details)
return detailsPromise
}
定义好方法之后,需要把该方法导出。
export {
getFundingDetail
}
(3)修改CreatorFundingTab.js文件,在componentWillMount方法中调用上面方法获取合约详情,然后再把获取到的结果保存在state中。
import {getFundingDetail} from '../eth/interaction'
state = {
// creatorFundings : [],
creatorFundingDetail : []
}
async componentWillMount() {
// 获取当前用户地址
let accounts = await web3.eth.getAccounts()
// 调用合约的getCreatorFunding方法,获取我发起的众筹合约,该方法返回一个合约数组
let creatorFundings = await factoryContractInstance.methods.getCreatorFunding().call({
from : accounts[0]
})
// 获取合约详情的Promise
let creatorFundingDetail = getFundingDetail(creatorFundings)
this.setState({
// creatorFundings
creatorFundingDetail
})
}
四、搭建合约列表界面
(1)在display目录下新建common子目录,然后在子目录下新建CardList.js文件。
(2)在文件中定义一个CardList方法,该方法负责把details中的合约信息设置到Card组件中。关于semantic-ui的card组件的具体用法,请自行参考:https://react.semantic-ui.com/views/card/#variations-colored
import React from 'react'
import {Card, Image, Icon, List, Progress} from 'semantic-ui-react'
const src = '/images/elliot.jpg'
const CardList = (props) => {
// 遍历众筹合约
let details = props.details
let cards = details.map(detail => {
// 获取合约信息,并且设置到Card组件中
return <CardExampleCard/>
})
return (
<Card.Group itemsPerRow={4}>
{cards}
</Card.Group>
)
}
const CardExampleCard = () => (
<div>
<Card>
<Image src='/images/elliot.jpg'/>
<Card.Content>
<Card.Header>发动机</Card.Header>
<Card.Meta>
<span>剩余时间:00秒</span>
<Progress indicating percent='50' size='small' progress/>
</Card.Meta>
<Card.Description>物超所值!!!</Card.Description>
</Card.Content>
<Card.Content extra>
<List divided horizontal style={{display: 'flex', justifyContent: 'space-around'}}>
<List.Item>
<List.Content>
<List.Header>已筹</List.Header>
<List.Description>1000 eth</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>已达</List.Header>
<List.Description>50%</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>参与人数</List.Header>
<List.Description>500人</List.Description>
</List.Content>
</List.Item>
</List>
</Card.Content>
</Card>
</div>
)
export default CardList
(2)修改CreatorFundingTab.js文件,在rendor方法加载CardList组件,并且把creatorFundingDetail传递给CardList中。
import CardList from './common/CardList'
class CreatorFundingTab extends Component {
render() {
return (
<div>
<CardList details={this.state.creatorFundingDetail}/>
</div>
)
}
}
(3)显示Card数据
第一步:修改interaction.js文件中的getCreatorFundingDetail方法,把findingAddress、balance、investorCount封装到结构体中对象中。
let getFundingDetail = (fundings) => {
let details = fundings.map(async function(fundingAddress) {
...
let balance = await fundingContractInstance.methods.getBalance().call()
let investorCount = await fundingContractInstance.methods.getInvestorCount().call()
// 3.创建结构体对象
let detail = {
fundingAddress,
...
balance,
investorCount}
return detail
})
...
}
第二步:修改CardList.js文件的CardList方法,把detail对象传给CardExampleCard中。
const CardList = (props) => {
...
let cards = details.map(detail => {
// 获取合约信息,并且设置到Card组件中
return <CardExampleCard key={detail.fundingAddress} detail={detail}/>
})
...
}
第三步:修改CardExampleCard方法,把detail中的数据结构出来。
// 解构出props.detail中的数据
let detail = props.detail
const {contractAddress, manager, projectName, targetMoney, supportMoney, leftTime, balance, investorCount}
计算已众筹金额的百分比,以及把balance的单位转换成eth。
// 计算已筹金额的百分比
let percentage = (parseFloat(balance) / parseFloat(targetMoney)).toFixed(2) * 100
// 把balance单位转换成eth
let accountBalance = parseFloat(balance) / 10**18
最后把结构出来的数据填充到相应位置。
return (
<div>
<Card>
<Image src='/images/elliot.jpg'/>
<Card.Content>
<Card.Header>{projectName}</Card.Header>
<Card.Meta>
<span>剩余时间:{leftTime}秒</span>
<Progress indicating percent={percentage} size='small' progress/>
</Card.Meta>
<Card.Description>不容错过</Card.Description>
</Card.Content>
<Card.Content extra>
<List divided horizontal style={{display: 'flex', justifyContent: 'space-around'}}>
<List.Item>
<List.Content>
<List.Header>已筹</List.Header>
<List.Description>{accountBalance}eth</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>已达</List.Header>
<List.Description>{percentage}%</List.Description>
</List.Content>
</List.Item>
<List.Item>
<List.Content>
<List.Header>参与人数</List.Header>
<List.Description>{investorCount}人</List.Description>
</List.Content>
</List.Item>
</List>
</Card.Content>
</Card>
</div>
)
至此,我发起众筹界面的展示已经完成,效果如下图所示:
五、所有众筹和我参与过的众筹
所有众筹和我参与过的众筹的实现非常类似,所以AllFundingTab和SupportorFundingTab代码都是从CreatorFundingTab中复制过来稍作改动。代码如下:
(1)AllFundingTab.js的实现
import React, {Component} from 'react';
import {factoryContractInstance, fundingContractInstance} from '../eth/instance'
import {getFundingDetail} from '../eth/interaction'
import web3 from '../utils/InitWeb3'
import CardList from './common/CardList'
class AllFundingTab extends Component {
state = {
allFundingDetail : []
}
async componentWillMount() {
// 获取当前用户地址
let accounts = await web3.eth.getAccounts()
// 调用合约方法,获取所有的众筹合约,该方法返回一个合约数组
let allFundings = await factoryContractInstance.methods.getAllFundings().call({
from : accounts[0]
})
// 获取合约详情的Promise
let allFundingDetail = await getFundingDetail(allFundings)
this.setState({
allFundingDetail
})
}
render() {
return (
<div>
<CardList details={this.state.allFundingDetail}/>
</div>
)
}
}
export default AllFundingTab
(2)SupportorFundingTab的实现
import React, {Component} from 'react';
import {factoryContractInstance, fundingContractInstance} from '../eth/instance'
import {getFundingDetail} from '../eth/interaction'
import web3 from '../utils/InitWeb3'
import CardList from './common/CardList'
class SupportorFundingTab extends Component {
state = {
supportorFundingDetail : []
}
async componentWillMount() {
// 获取当前用户地址
let accounts = await web3.eth.getAccounts()
// 调用合约方法,获取我支持的众筹合约,该方法返回一个合约数组
let allFundings = await factoryContractInstance.methods.getSupportorFunding().call({
from : accounts[0]
})
// 获取合约详情的Promise
let supportorFundingDetail = await getFundingDetail(allFundings)
this.setState({
supportorFundingDetail
})
}
render() {
return (
<div>
<CardList details={this.state.supportorFundingDetail}/>
</div>
)
}
}
export default SupportorFundingTab