dapp应用开发入门实战-商城1

98 阅读2分钟

dapp开发有一段时间了,发现网上学习资料极少,本着分享的原则,笔者也计划通过商城实例来给大家讲解下dapp开发应用。废话不多说,先上效果:

运行效果

image.png

image.png

image.png

技术栈: 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

image.png

  • 创建metamask测试网络

image.png

step1

image.png step2

image.png 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数据收藏,添加、取消最基础功能,在后续的文章中会基于此继续进行更新完善,最终目标是做成一个带支付功能的完整商城,敬请期待,谢谢!