NFT-前端开发(一)

使用

  • 在我们想要保存项目的目录下打开终端运行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命令,即可出现以下界面,但是目前我们还没有连接到合约那些,因此还不能实现其它功能哈,后续会实现滴。

    😊未完待续~
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值