dapp开发有一段时间了,发现网上学习资料极少,本着分享的原则,笔者也计划通过商城实例来给大家讲解下dapp开发应用。废话不多说,先上效果:
运行效果
技术栈: truffle+React(antd)
环境搭建
npm install -g truffle
npm install -g ganache-cli
工程初始化
truffle init
contracts/: Solidity合约目录
migrations/: 部署脚本文件目录
test/: 测试脚本目录
truffle.js: Truffle 配置文件
创建智能合约
./contracts/Market.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract Market {
mapping(address => string[]) orderIds;
mapping(address => string[]) storeIds;
function buyGood(string calldata goodId) public {
orderIds[msg.sender].push(goodId);
}
function storeGood(string calldata goodId) public {
storeIds[msg.sender].push(goodId);
}
function getOrderIds(address _add) public view returns (string[] memory) {
return orderIds[_add];
}
function getStoreIds(address _add) public view returns (string[] memory) {
return storeIds[_add];
}
function removeStore(address _add, string calldata goodId) public {
string[] storage ids = storeIds[_add];
uint256 index;
for (uint256 i = 0; i < ids.length; i++) {
if (keccak256(bytes(ids[i])) == keccak256(bytes(goodId))) {
// string比较需要转换
index = i;
break;
}
}
for (uint256 j = index; j < ids.length - 1; j++) {
ids[j] = ids[j + 1];
}
ids.pop();
}
function removeOrder(address _add, string calldata goodId) public {
string[] storage ids = orderIds[_add];
uint256 index;
for (uint256 i = 0; i < ids.length; i++) {
if (keccak256(bytes(ids[i])) == keccak256(bytes(goodId))) {
// string比较需要转换
index = i;
break;
}
}
for (uint256 j = index; j < ids.length - 1; j++) {
ids[j] = ids[j + 1];
}
ids.pop();
}
}
MetaMask网络连接
- chrome浏览器安装 metamask插件
- 本地启动Ganche
- 创建metamask测试网络
step1
step2
step3(注: url换成你本地的端口号)
修改合约配置文件truffle-config.js
module.exports = {
networks: {
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 7545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
},
},
compilers: {
solc: {
version: "0.8.17",// 用匹配的合约版本进行编译
}
},
};
编译部署合约
truffle compile;
truffle migrate;
编译完成以后在build/contracts目录下生成Market.json abi文件,这个在前端工程中要引入使用
前端核心代码
import React, { useState, useEffect } from 'react';
import { Tabs } from 'antd';
import Shop from './components/Shop';
import Order from './components/Order';
import Store from './components/Store';
import { tabs } from './mock';
import './index.less'
import Web3 from 'web3';
import Adoption from '../../config/Market.json'
let web3 = null, instance = null, accountGlo = null;
const createInstance = async () => {
if (window.ethereum) {
// use MetaMask's provider
web3 = new Web3(window.ethereum);
// await window.ethereum.enable(); // get permission to access accounts
await window.ethereum.request({ method: 'eth_requestAccounts' })
} else {
web3 = new Web3(
new Web3.providers.HttpProvider("http://127.0.0.1:7545"),
);
}
// setWeb3(web3);
try {
const networkId = await web3.eth.net.getId();
const deployedNetwork = Adoption.networks[networkId];
instance = new web3.eth.Contract(
Adoption.abi,
deployedNetwork.address,
);
const accounts = await web3.eth.getAccounts();
accountGlo = accounts[0]
} catch (error) {
console.error("Could not connect to contract or chain.");
}
}
function Market () {
const [activeKey, setActiveKey] = useState('1');
const [account, setAccount] = useState(null);
const [orders, setOrders] = useState([]);
const [stores, setStores] = useState([]);
const onChange = (key) => {
setActiveKey(key);
};
const connectWeb3 = async () => {
await createInstance()
setAccount(accountGlo)
refreshData()
}
useEffect(() => {
connectWeb3();
})
const refreshData = async () => {
const { getStoreIds, getOrderIds } = instance.methods;
const ids = await getOrderIds(accountGlo).call();
setOrders(ids);
const ids2 = await getStoreIds(accountGlo).call();
setStores(ids2);
}
const addGood = async (goodObj) => {
const { buyGood } = instance.methods;
await buyGood(goodObj.id).send({ from: accountGlo });
refreshData();
}
const addStore = async (goodObj) => {
const { storeGood } = instance.methods;
await storeGood(goodObj.id).send({ from: accountGlo });
refreshData();
}
const deleteOrder = async (goodObj) => {
const { removeOrder } = instance.methods;
await removeOrder(accountGlo, goodObj.id).send({ from: accountGlo });
refreshData();
}
const deleteStore = async (goodObj) => {
const { removeStore } = instance.methods;
await removeStore(accountGlo, goodObj.id).send({ from: accountGlo });
refreshData();
}
return (
<>
<Tabs defaultActiveKey={activeKey} activeKey={activeKey} items={tabs} onChange={onChange} />
{activeKey === '1' ? <Shop account={account} buyGood={addGood} storeGood={addStore} orders={orders} stores={stores} /> : null}
{activeKey === '2' ? <Order orders={orders} deleteOrder={deleteOrder} /> : null}
{activeKey === '3' ? <Store stores={stores} deleteStore={deleteStore}/> : null}
</>
);
}
export default Market
至此一个简单的dapp应用就开发完毕了,运行前端工程即可看到对应效果,代码会在后期整理好后传到git,大家可以下载完整源码查看。
完善计划
目前不涉及商品价格支付,仅仅是一个简单消耗Gas数据收藏,添加、取消最基础功能,在后续的文章中会基于此继续进行更新完善,最终目标是做成一个带支付功能的完整商城,敬请期待,谢谢!