NFT-前端开发(一)

news2025/1/9 20:59:53

使用

  • 在我们想要保存项目的目录下打开终端运行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.coloradmin.cn/o/1559238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++教学——从入门到精通 5.单精度实数float

众所周知&#xff0c;三角形的面积公式是(底*高)/2 那就来做个三角形面积计算器吧 到吗如下 #include"bits/stdc.h" using namespace std; int main(){int a,b;cin>>a>>b;cout<<(a*b)/2; } 这不对呀&#xff0c;明明是7.5而他却是7&#xff0c;…

(一)kafka实战——kafka源码编译启动

前言 本节内容是关于kafka消息中间键的源码编译&#xff0c;并通过idea工具实现kafka服务器的启动&#xff0c;使用的kafka源码版本是3.6.1&#xff0c;由于kafka源码是通过gradle编译的&#xff0c;以及服务器是通过scala语言实现&#xff0c;我们要预先安装好gradle编译工具…

暴力破解笔记

1 暴力破解简介 暴力破解&#xff1a; 蛮力攻击&#xff0c;又称为穷举攻击&#xff0c;或暴力破解&#xff0c;将密码进行逐个尝试验证&#xff0c;直到尝试出真正的密码为止。 暴力破解是指采用反复试错的方法并希望最终猜对&#xff0c;以尝试破解密码或用户名或找到隐藏的…

进无止境,砥砺前行,互联网营销专家杨建允助力多个品牌和机构提升营运效率!

进无止境&#xff0c;砥砺前行&#xff01;在过去的一年&#xff08;2023年&#xff09;&#xff0c;互联网营销专家杨建允持续为数十家品牌和机构提供了品牌和营销支持&#xff01;统计如下&#xff1a;&#xff08;排名不分先后&#xff09; 滴滴&#xff1b; 企查查&#…

了解XSS和CSRF攻击与防御

什么是XSS攻击 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络安全漏洞&#xff0c;它允许攻击者在受害者的浏览器上执行恶意脚本。这种攻击通常发生在 web 应用程序中&#xff0c;攻击者通过注入恶意脚本来利用用户对网站的信任&…

属性选择器

1.[title]{background:yellow;}&#xff1a;所有带title标签设置成黄色 2.div[class]{background:yellow;}&#xff1a;所有div中带class标签设置成黄色 3.div[classbox1]{border:1px solid blue; }&#xff1a;div中包含class并且classbox1的设置成蓝边框 4. class…

【Linux】进程实践项目 —— 自主shell编写

送给大家一句话&#xff1a; 不管前方的路有多苦&#xff0c;只要走的方向正确&#xff0c;不管多么崎岖不平&#xff0c;都比站在原地更接近幸福。 —— 宫崎骏《千与千寻》 自主shell命令编写 1 前言2 项目实现2.1 创建命令行2.2 获取命令2.3 分割命令2.4 运行命令 3 源代码…

python mysql错误如何处理

错误代码类型&#xff1a;pymysql.err.InternalError: (1054, "Unknown column jack in field list") import pymysql d_mysql {host: 127.0.0.1, port: 33333,user: *****,password: *****,db: *****,charset: utf8} conn pymysql.connect(**d_mysql) cur co…

基于SSM远程同步课堂系统

基于SSM远程同步课堂系统的设计与实现 摘要 在这样一个网络数据大爆炸的时代&#xff0c;人们获取知识、获取信息的通道非常的多元化&#xff0c;通过网络来实现数据信息的获取成为了现在非常常见的一种方式&#xff0c;而通过网络进行教学&#xff0c;在网络上进行远程的课堂…

C++--内联函数

当调用一个函数时&#xff0c;程序就会跳转到该函数&#xff0c;函数执行完毕后&#xff0c;程序又返回到原来调用该函数的位置的下一句。 函数的调用也需要花时间&#xff0c;C中对于功能简单、规模小、使用频繁的函数&#xff0c;可以将其设置为内联函数。 内联函数&#xff…

IAG—热门单曲

喜欢你&#x1f60d; 泡沫&#x1fae7; 倒数 ... 太多了 句号 多远都要在一起 来自天堂的魔鬼 再见 ……

车载电子电器架构 —— 通信信号数据库开发

车载电子电器架构 —— 信号数据库开发 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自…

芒果YOLOv8改进130:Neck篇,即插即用,CCFM重构跨尺度特征融合模块,构建CCFM模块,助力小目标检测涨点

芒果专栏 基于 CCFM 的改进结构,改进源码教程 | 详情如下🥇 💡本博客 改进源代码改进 适用于 YOLOv8 按步骤操作运行改进后的代码即可 即插即用 结构。博客 包括改进所需的 核心结构代码 文件 YOLOv8改进专栏完整目录链接:👉 芒果YOLOv8深度改进教程 | 🔥 订阅一个…

TCP通信——端口转发(重点内容)

实现多人群聊 Client(客户端&#xff09;建立通信 package com.zz.tcp.case1;import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.Scanner;public class Client {public static void mai…

论文笔记:GPT-4 Is Too Smart To Be Safe: Stealthy Chat with LLMs via Cipher

ICLR 2024 reviewer评分 5688 1 论文思路 输入转换为密码&#xff0c;同时附上提示&#xff0c;将加密输入喂给LLMLLM输出加密的输出加密的输出通过解密器解密 ——>这样的步骤成功地绕过了GPT-4的安全对齐【可以回答一些反人类的问题&#xff0c;这些问题如果明文问的话&…

Linux(CentOS 7 )基于git、maven实现springboot自动化部署

前提 1、已安装git、maven、java环境 不清楚的可以看另一篇文章&#xff1a; https://blog.csdn.net/weixin_44646763/article/details/137041469 2、已为项目设置远程 git 仓库 origin (可以通过&#xff1a;git remote add origin https://github.com/xxx/xxx.git设置) 创…

【Linux】详解软硬链接

一、软硬链接的建立方法 1.1软链接的建立 假设在当前目录下有一个test.txt文件&#xff0c;要对其建立软链接&#xff0c;做法如下&#xff1a; ln就是link的意思&#xff0c;-s表示软链接&#xff0c;test.txt要建立软链接的文件名&#xff0c;后面跟上要建立的软链接文件名…

设计模式深度解析:AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 AI如何影响装饰器模式与组合模式的选择与应用 在今天这个快速发展的技术时代&#…

C语言-文件操作

&#x1f308;很高兴可以来阅读我的博客&#xff01;&#x1f31f;我热衷于分享&#x1f58a;学习经验&#xff0c;&#x1f3eb;多彩生活&#xff0c;精彩足球赛事⚽&#x1f517;我的CSDN&#xff1a; Kevin ’ s blog&#x1f4c2;专栏收录&#xff1a;C预言 1. 文件的作用 …

浅析AI大模型当前存在的技术瓶颈和限制及解决方案

方向五&#xff1a;未来发展趋势与挑战 提示&#xff1a;展望AI大模型学习的未来发展趋势&#xff0c;并讨论当前面临的主要挑战。可以关注新技术、新方法的出现&#xff0c;以及它们对AI大模型学习的影响&#xff1b;同时&#xff0c;也可以分析当前存在的技术瓶颈和限制&…