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命令,即可出现以下界面,但是目前我们还没有连接到合约那些,因此还不能实现其它功能哈,后续会实现滴。

    😊未完待续~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/503050.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 基于chrony进行时钟同步方案验证

Linux 基于chrony进行时钟同步方案验证 1. 背景介绍2. 验证过程2.1 追踪配置2.2 追平记录2.2 追平时间换算 3. 疑问和思考3.1 如何统计追踪1s需要花费多长时间&#xff1f; 4. 参考文档 chrony是一个Linux系统中用于时钟同步的工具。它使用NTP&#xff08;网络时间协议&#xf…

HarmonyOS 应用开发之模型切换

本文介绍如何将一个FA模型开发的声明式范式应用切换到Stage模型&#xff0c;您需要完成如下动作&#xff1a; 工程切换&#xff1a;新建一个Stage模型的应用工程。 配置文件切换&#xff1a;config.json切换为app.json5和module.json5。 组件切换&#xff1a;PageAbility/Serv…

9.2-源码分析:Dubbo Remoting 层 Buffer 缓冲区

Buffer 是一种字节容器&#xff0c;在 Netty 等 NIO 框架中都有类似的设计&#xff0c;例如&#xff0c;Java NIO 中的ByteBuffer、Netty4 中的 ByteBuf。Dubbo 抽象出了 ChannelBuffer 接口对底层 NIO 框架中的 Buffer 设计进行统一&#xff0c;其子类如下图所示&#xff1a; …

私域流量:如何给微信客户贴上精准标签?

私域流量在现代营销中变得越来越重要&#xff0c;而给微信客户贴上精准标签是私域流量管理的一个关键环节。今天就给大家分享三个给客户贴上精准标签的小技巧&#xff0c;一起来看看吧&#xff01; 首先&#xff0c;我们可以通过设定静态标签来给微信客户贴上精准标签。这意味…

初识C++(四)深入了解拷贝构造函数

1.拷贝构造函数 拷贝构造函数是一种特殊的构造函数&#xff0c;在对象需要以同一类的另一个对象为模板进行初始化时被调用。它的主要用途是初始化一个对象&#xff0c;使其成为另一个对象的副本 class Date { public:Date(int year 1, int month 1, int day 1){_year yea…

JAVAEE之网络原理

1.IP地址 IP地址主要用于标识网络主机、其他网络设备&#xff08;如路由器&#xff09;的网络地址。简单说&#xff0c;IP地址用于定位主机的网络地址。 格式 IP地址是一个32位的二进制数&#xff0c;通常被分割为4个“8位二进制数”&#xff08;也就是4个字节&#xff09;&…

P15:PATH环境变量

为什么要配置环境变量 当我们打开DOS窗口&#xff0c;输入&#xff1a;javac&#xff0c;出现下面问题。 原因&#xff1a;windows操作系统在当前目录中无法找到javac命令文件。Windows操作系统是如何搜索硬盘上某一个命令&#xff1f; 首先从当前目录中搜索该命令如果当前目录…

LeetCode---390周赛

题目列表 3090. 每个字符最多出现两次的最长子字符串 3091. 执行操作使数据元素之和大于等于 K 3092. 最高频率的 ID 3093. 最长公共后缀查询 一、每个字符最多出现两次的最长子字符串 非常经典的滑动窗口问题&#xff0c;即动态维护一段区间&#xff0c;使得这段区间满足…

代码随想录-二叉树(路径)

目录 257. 二叉树的所有路径 题目描述&#xff1a; 输入输出描述&#xff1a; 思路和想法&#xff1a; 404. 左叶子之和 题目描述&#xff1a; 输入输出描述&#xff1a; 思路和想法&#xff1a; 513.找树左下角的值 题目描述&#xff1a; 输入输出描述&#xff1a;…

刷爆LeetCode:两数之和 【1/1000 第一题】

&#x1f464;作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 作者专栏每日更新&#xff1a;LeetCode解锁1000题: 打怪升级之旅https://blog.csdn.net/cciehl/category…

如何在OceanBase的OCP多节点上获取日志

背景 在使用OceanBase的OCP的过程中&#xff0c;因各种因素&#xff0c;我们可能需要对当前页面进行跟踪。在单一ocp节点环境下&#xff0c;我们自然可以直接在该节点上查找所需的日志。然而&#xff0c;当我们的环境中部署了多个ocp节点时&#xff0c;在排查问题时就会变得相…

让机器理解语言,从字词开始,逐步发展到句子和文档理解:独热编码、word2vec、词义搜索、句意表示、暴力加算力

让机器理解语言&#xff0c;从字词开始&#xff0c;逐步发展到句子和文档理解&#xff1a;独热编码、词嵌入、word2vec、词义搜索、句意表示、暴力加算力 独热编码&#xff1a;分类 二进制特征Word2Vec 词嵌入&#xff1a; 用低维表示 用嵌入学习 用上下文信息Skip-gram 跳字…

工业测试测量仪器与人工智能(AI)如何结合

工业测试测量仪器与人工智能&#xff08;AI&#xff09;的结合可以通过多种方式实现&#xff0c;其中一些主要方法包括&#xff1a; 1. 数据分析和预测 智能数据分析&#xff1a;利用AI算法对从传感器和测试仪器收集的数据进行分析&#xff0c;识别模式、趋势和异常&#xff0…

RVM安装ruby笔记

环境 硬件&#xff1a;Macbook Pro 系统&#xff1a;macOS 14.1 安装公钥 通过gpg安装公钥失败&#xff0c;报错如下&#xff1a; 换了几个公钥地址&#xff08;hkp://subkeys.pgp.net&#xff0c;hkp://keys.gnupg.net&#xff0c;hkp://pgp.mit.edu&#xff09;&#xff0c;…

瑞吉外卖实战学习--6、通过try和catch进行异常处理

try和catch进行异常处理 效果图前言1、公共拦截器进行异常处理1.1、创建公共报错处理的方法1.2、@ControllerAdvice中设置要拦截的类1.3、@ExceptionHandler中写处理的异常类2、完善错误拦截器2.1、效果效果图 前言 当用户名重复数据库会报错,此时就需要捕获异常操作 1、公共…

LM算法探寻——答案在022浙江大学信号与系统

LM算法详解 | 宇尘 (gitee.io) 求函数最小值&#xff0c;从另一个角度理解是求误差最小值。 梯度 最陡梯度下降算法和LMS算法原理介绍及MATLAB实现_lms滤波器中的梯度下降-CSDN博客 均值即平均值 (3 封私信 / 56 条消息) FIR滤波器中的冲激响应怎么理解&#xff1f; 和滤波有…

查找某数据在单链表中出现的次数

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct LinkNode {ElemType data;LinkNode* next; }LinkNode, * LinkList; //尾插法建立单链表 void creatLinkList(LinkList& L) {L (LinkNode*)mallo…

微分方程错题本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

ssm008医院门诊挂号系统+jsp

医院门诊挂号系统 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;医院门诊挂号系统当然也不能排除在外。医院门诊挂号系统是以实际运用为开发背景&#xff0c;运用软件…

笔迹/签名数据集汇总

这里只收集公开/易申请的数据集 数据集发表年份语言最小单元Writers/人规模颜色最小单元文件格式示例图片备注CSAFE Handwriting Database2019英语页9090 人*(3 次*9 个样本) 2430 页300 dpi 扫描png-HWDB2.0-2.22011汉字页1,019每人 5 页,共 5091 页灰度图dgrl-CEDAR2006英语…