NFT-前端开发(一)

news/2024/7/7 14:32:37 标签: 区块链, react.js

使用

  • 在我们想要保存项目的目录下打开终端运行npx create-react-app test2命令初始化,test2是我们的项目名字,可以自己去更改。
    在这里插入图片描述
  • 初始化完成后,我们目录下就会多出一个test2文件夹 ,然后我们在vscode中打开该文件夹
  • 然后我们打开javascript终端,在终端输入npm run start命令打开一个网页,这就是初始化项目后它原始的一个界面。
    在这里插入图片描述
    在这里插入图片描述
  • 到目前为止我们需要五个界面,ipfs节点启动界面,连接remix界面,react前端启动界面,后端启动界面,hardhat节点启动界面。
  • 新建一个Navbar.js文件来新建组件,代码如下
function Navbar() {
  return (
    <nav className="navbar">
      <div className="navbar-brand">NFT Marketplace</div>
      <div className="navbar-menu">
        <button className="connect-wallet-button">Connect Wallet</button>
      </div>
    </nav>
  )
}

export default Navbar;
  • 更改APP.js代码如下
import './App.css';
import Navbar from './Navbar.js';
import { useEffect, useState } from 'react';

function App() {
  //用于在react组件中声明一个状态变量walletAddress和一个更新该状态的函数setWalletAddress,将其初始化为空字符串
  const [walletAddress, setWalletAddress] = useState("");

  //useEffect用于在函数组件渲染完成后执行副作用操作,这里就是在组件渲染完成后立即获取用户的以太坊钱包地址
  useEffect(() => {
    getWalletAddress();
  }, []);
  //用于获取以太坊钱包地址
  async function getWalletAddress() {
    //先检查当前环境是否存在以太坊的对象
    if (window.ethereum) {
      //发起一个请求,提示用户授权以太坊账户连接,如果用户授权成功则返回数组中的第一个账户
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      const account = accounts[0];
      //更新状态变量
      setWalletAddress(account);
    } else {
      //如果当前环境中不存在以太坊的对象,则显示一个警告,提示用户安装MetaMask
      alert("Please install MetsMask");
    }
  }


  return (
    <div className="container">
      <Navbar />
      <p>{walletAddress}</p>
    </div>
  );
}

export default App;

  • 将APP.css文件代码更改如下
.App {
  text-align: center;
}

#container {
  /* width: 180vh; */
  /* border: 4px dashed rgba(4, 4, 5, 0.1); */
  min-height: 160px;
  padding: 32px;
  position: relative;
  border-radius: 16px;
  -webkit-box-align: center;
  align-items: center;
  -webkit-box-pack: center;
  justify-content: center;
  flex-direction: column;
  text-align: left;
  word-break: break-word;
}

.upload-container {
  max-width: 600px;
  margin: 0 auto;
  margin-top: 50px;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.upload-form {
  display: flex;
  flex-direction: column;
}

.upload-form label {
  margin-top: 10px;
}

.upload-form input,
.upload-form textarea {
  padding: 10px;
  margin-top: 5px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.upload-form .buttons {
  display: flex;
  justify-content: space-between;
  margin-top: 20px;
}

.cancel-button,
.upload-button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.cancel-button {
  background: #ccc;
}

.upload-button {
  background: #007bff;
  color: white;
}

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  background-color: #333;
  color: white;
}

.navbar-brand {
  font-size: 1.5rem;
}

.navbar-menu {
  display: flex;
  align-items: center;
}

.connect-wallet-button {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background-color: #007bff;
  color: white;
}

input#title::placeholder {
  font-family: sans-serif;
  /* Replace with your desired font family */
  font-size: 16px;
  /* Replace with your desired font size */
  color: #a9a9a9;
  /* Replace with your desired color */
}

/* Change the placeholder font style for description textarea */
textarea#description::placeholder {
  font-family: sans-serif;
  /* Replace with your desired font family */
  font-size: 16px;
  /* Replace with your desired font size */
  color: #a9a9a9;
  /* Replace with your desired color */
}

.nft-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
  padding: 1rem;
}

.nft-card {
  border: 1px solid #e1e1e1;
  border-radius: 10px;
  overflow: hidden;
}

.nft-image img {
  width: 100%;
  height: auto;
  display: block;
}

.nft-info {
  padding: 0.5rem;
  text-align: center;
}

.nft-detail {
  display: flex;
  max-width: 600px;
  margin: 0 auto;
  margin-top: 50px;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.nft-image {
  flex: 1;
}

.nft-info {
  flex: 1;
  text-align: left;
}

.navbar a {
  color: white;
}
  • 然后我们来测试下,看看能不能返回账户地址,运行后返回网页,然后连接到我们的MetaMask,就能得到返回的地址
    在这里插入图片描述
  • 新建一个组件名为UploadSuccess.js
  • 新建一个名为UploadImage.js的文件
  • npm install react-router-dom命令安装库
  • npm install axios库安装

以下是前端开发一修改完后的代码

  • Navbar.js
//定义函数组件,接收两个属性,返回一个JSX元素作为组件的UI渲染结果
//这里主要就是定义我们的页面,类似于html,将该组件导入到App.js中后再导入App.css样式进行渲染就能完成该页面效果
//这里传入了函数和地址
function Navbar({ onConnectWallet, address }) {
  return (
    <nav className="navbar">
      <div className="navbar-brand">NFT Marketplace</div>
      <div className="navbar-menu">
        {/* 点击该按钮时,调用传入的onConnectWallet函数,然后显示地址 */}
        <button className="connect-wallet-button" onClick={onConnectWallet}>{address.slice(0, 8) || "Connect Wallet"}</button>
      </div>
    </nav>
  )
}

export default Navbar;
  • APP.js
import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import './App.css';

import UploadImage from './UploadImage.js';
import Navbar from './Navbar.js';
import UploadSuccess from './UploadSuccess.js';


function App() {
  //用于在react组件中声明一个状态变量walletAddress和一个更新该状态的函数setWallet,将其初始化为空字符串
  const [walletAddress, setWallet] = useState("");

  //useEffect用于在函数组件渲染完成后执行副作用操作,这里就是在组件渲染完成后执行addWalletListener函数
  useEffect(() => {
    //getWalletAddress();
    addWalletListener();
    ;
  }, []);


  //用于钱包切换地址时网页更新地址
  function addWalletListener() {
    if (window.ethereum) {
      window.ethereum.on("accountsChanged", (accounts) => {
        if (accounts.length > 0) {
          setWallet(accounts[0]);
        } else {
          setWallet("");
        }
      });
    }
  }

  //用于获取以太坊钱包地址
  const getWalletAddress = async () => {
    //先检查当前环境是否存在以太坊的对象
    if (window.ethereum) {
      //发起一个请求,提示用户授权以太坊账户连接,如果用户授权成功则返回数组中的第一个账户
      try {
        const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
        setWallet(accounts[0]);
      } catch (error) {
        console.error('Error  connecting to wallet', error);
      }
    }
  };


  return (
    <div id="container">
      {/* Router是React Router库提供的顶层路由组件,用于包裹整个应用的路由配置,提供路由的上下文,让应用能根据URL路径来渲染不同的组件 */}
      <Router>
        <Navbar onConnectWallet={getWalletAddress} address={walletAddress} />

        {/* 用于定义路由的规则 */}
        <Routes>
          {/* 用于指定不同路径下的组件渲染,这里指定了当URL路径为 "/" 时要渲染的组件。path 属性表示匹配的路径,
          exact 属性表示只有当URL路径完全匹配时才渲染该组件。element 属性指定了要渲染的React元素,
          这里是 <UploadImage> 组件,并传递了一个 address 属性给它。 */}
          <Route path="/" exact element={<UploadImage address={walletAddress} />} />
          <Route path="/success" element={<UploadSuccess />} />
        </Routes>
      </Router>
    </div>
  );
};

export default App;
  • UploadSuccess.js
//定义上传成功的页面的组件
const UploadSuccess = () => {
  return (
    <div>
      <h1>Upload Successfully</h1>
      <p>Your image has been uploaded to IPFS successfully!</p>
    </div>
  );
};

export default UploadSuccess;
  • UploadImage.js
//也是一个react组件,主要用于上传图片到IPFS并创建NFT
//这俩hook在组件中用于用于管理状态和获取DOM元素的引用
import React, { useState, useRef } from 'react';
//该hook用于在组件中进行页面导航
import { useNavigate } from 'react-router-dom';
//用于进行HTTP请求
import axios from 'axios';
//给组件添加样式
import './App.css';

function UploadImage({ address }) {
  //声明状态变量并初始化为空字符串,setTitle用来更新状态变量的
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  //声明一个引用变量,初始化为null。用于获取文件输入框中的DOM元素
  const fileInputRef = useRef(null);
  //创建一个导航函数,用于在页面中进行导航
  const navigate = useNavigate();

  //取消上传操作的处理函数,用于清空标题和描述,并重置文件输入框
  const handleCancel = () => {
    //更新状态变量为空字符串
    setTitle('');
    setDescription('');
    if (fileInputRef.current) {
      //文件输入框当前值为空字符串
      fileInputRef.current.value = "";
    }
  };

  //用于上传文件的处理函数,用户点击上传按钮时被调用
  const handleUpload = async (event) => {
    //阻止表单的默认提交行为
    event.preventDefault();
    //检查用户是否选择了要上传的文件
    if (fileInputRef.current.files.length === 0) {
      alert('Please select a file to upload.');
      return;
    }

    //创建一个新对象用于存储要上传的数据
    const formData = new FormData();
    //将要上传的数据添加到该对象中
    formData.append('title', title);
    formData.append('description', description);
    formData.append('file', fileInputRef.current.files[0]);
    formData.append('address', address);

    try {
      //使用 Axios 发起 HTTP POST 请求将 FormData 对象发送到指定的服务器地址 'http://127.0.0.1:3000/upload'。
      //在请求中,我们设置了请求头 'Content-Type': 'multipart/form-data',以确保服务器能够正确地处理文件上传。
      const response = await axios.post('http://127.0.0.1:3000/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });


      console.log('File uploaded successfully', response.data);
      //将页面导航到/success,提示用户文件上传成功
      navigate('/success');
    } catch (error) {
      //打印上传失败的消息
      console.error('Error uploading file:', error);
    }
  };

  return (
    <div className="upload-container">
      <h1>Upload Image to IPFS and Mint NFT</h1>
      {/* 当用户提交表单时handleUpload函数将被调用 */}
      <form className="upload-form" onSubmit={handleUpload}>
        <label htmlFor="title">Title *</label>
        <input
          type="text"
          id="title"
          placeholder="Enter image title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          required
        />

        <label htmlFor="description">Description</label>
        <textarea
          id="description"
          placeholder="Describe your image"
          value={description}
          onChange={(e) => setDescription(e.target.value)}
        />

        <label htmlFor="file">Image *</label>
        <input
          type="file"
          id="file"
          ref={fileInputRef}
          required
        />

        <div className="buttons">
          <button type="button" className="cancel-button" onClick={handleCancel}>Cancel</button>
          <button type="submit" className="upload-button">Upload</button>
        </div>
      </form>
    </div>
  );
}

export default UploadImage;
  • 然后我们在终端运行npm run start命令,即可出现以下界面,但是目前我们还没有连接到合约那些,因此还不能实现其它功能哈,后续会实现滴。

    😊未完待续~

http://www.niftyadmin.cn/n/5461174.html

相关文章

小狐狸ChatGPT付费AI创作系统V2.8.0独立版 + H5端 + 小程序前端

狐狸GPT付费体验系统的开发基于国外很火的ChatGPT&#xff0c;这是一种基于人工智能技术的问答系统&#xff0c;可以实现智能回答用户提出的问题。相比传统的问答系统&#xff0c;ChatGPT可以更加准确地理解用户的意图&#xff0c;提供更加精准的答案。同时&#xff0c;小狐狸G…

jupyter Notebook 默认路径修改

1. anaconda prompt 中运行 jupyter notebook --generate-config 命令&#xff0c;将在 C:\Users\Think\.jupyter文件下生成 jupyter_notebook_config.py 文件。 2.在jupyter_notebook_config.py 文件中&#xff0c;找c.NotebookApp.notebook_dir 这个变量&#xff0c; (1)若…

vue2封装dialog弹框

vue部分 DialogComponent.vue {{ title }} {{ content }} js部分 // dialog.js import Vue from ‘vue’;import DialogComponent from ‘./DialogComponent.vue’;export default function showDialog({ title, content }) { return new Promise((resolve, reject) > { c…

.NET CORE 分布式事务(四) CAP实现最终一致性

目录 引言&#xff1a; 1.0 最终一致性介绍 2.0 CAP 2.0 架构预览 3.0 .NET CORE 结合CAP实现最终一致性分布式事务 3.1 准备工作(数据库&#xff0c;本文使用的是MySql) 3.1.1 数据模型 3.1.2 DbContext 3.1.3 数据库最终生成 3.2 Nuget引入 3.3 appsettings.json …

【C++】C到C++的入门知识

目录 1、C关键字 2、命名空间 2.1 命名空间的定义 2.2 命名空间的使用 2.2.1 加命名空间名称及作用域限定符 2.2.2 使用using将命名空间中某个成员引入 2.2.3 使用using namespace 命名空间名称引入 3、C输入&输出 4、缺省参数 4.1 缺省参数的概念 4.2 缺省参数的…

UGUI 进阶

UI事件监听接口 目前所有的控件都只提供了常用的事件监听列表 如果想做一些类似长按&#xff0c;双击&#xff0c;拖拽等功能是无法制作的 或者想让Image和Text&#xff0c;RawImage三大基础控件能够响应玩家输入也是无法制作的 而事件接口就是用来处理类似问题 让所有控件都…

Go build 交叉编译-实现多平台兼容

【前言】 本章主要讲的是go程序如何兼容各种架构系统以及架构之间的区别 直接上干货&#xff0c;直接执行下面的指令&#xff0c;将GOOS&#xff08;平台&#xff09;和GOARCH&#xff08;架构&#xff09;就可以解决大部分架构兼容问题 GOOSlinux GOARCHamd64 go build test.g…

Python计算物理粒子及拉格朗日和哈密顿动力学

&#x1f3af;要点 运动和计算&#xff0c;牛顿运动定律&#xff0c;&#x1f3af;Python符号计算粒子速度随时间变化的微分方程&#xff0c;并绘制运动趋势图。单粒子一维物理运动&#xff0c;数学方程表示和计算&#xff1a;&#x1f3af;在重力作用下和空气阻力为线性&…