Reactjs + Nodejs + Express + Mongodb搭建[图片上传/预览]项目(上)

935 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

Reactjs + Nodejs + Express + Mongodb 搭建[图片上传/预览]前后端项目

概述

图片上传功能,在日常的项目开发中是很常见的功能,今天我们就来说说使用 Reactjs + Nodejs + Express + Mongodb 搭建前后端图片上传的例子

我们先看看完成后的效果

iShot_2022-05-27_23.10.44.gif

实现的功能:

  • 图片上传功能
  • 图片上传显示进度条功能
  • 图片预览功能
  • 图片列表功能
  • 图片下载功能

前端使用的技术:

  • Reactjs
  • Bootstrap
  • Axios

跟随本示例学习,你也可以搭建出来。

前端部分

前端项目结构

├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── public
│   └── index.html
└── src
    ├── App.css
    ├── App.js
    ├── components
    │   └── uploadImages.js
    ├── http-common.js     
    ├── index.js             
    └── services
        └── fileUpload.js 

配置 React 环境

这里我们使用 npx 创建一个 React 项目

npx create-react-app react-upload-images

完成后,cd 进入项目根目录

安装 Axios

npm install axios

安装完成后,我们使用命令 npm start 启动项目,可以看到 项目已经正常启动了

这里我们使用 bootstrap 的样式,导入 Bootstrap 到项目中

打开文件 public/index.html ,将以下代码添加到 head

<link type="text/css" rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" />

配置 Axios

src 文件夹下新建文件 http-common.js, 并添加如下内容

import axios from "axios";
export default axios.create({
  baseURL: "http://localhost:8080",
  headers: {
    "Content-type": "application/json"
  }
});

这里 baseURL 是你本地的地址

图片上传功能

前端项目的基本配置已经完成,接下来我们开始图片上传功能,在 src 文件下 创建如下文件

src/services/UploadFilesService.js,这个文件主要的作用就是和后端项目通讯,以进行文件的上传和文件列表数据的获取等

在文件中我们加入如下内容

import http from "../http-common";

class FileUploadService {
  upload(file, onUploadProgress) {
    let formData = new FormData();
    formData.append("file", file);
    return http.post("/upload", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
      onUploadProgress,
    });
  }

  getFiles() {
    return http.get("/files");
  }
}

export default new FileUploadService();
  • upload: 用于将文件数据以 post 请求格式,FormData 键值对的形式提交到后端, onUploadProgress 则是用于获取进度条数据
  • getFiles: 用于获取服务器上的文件列表数据

创建图片上传组件

图片上传组件要满足功能有 图片上传,进度条,预览,提示信息等

首先建一个文件,并引入 import UploadService from "../services/fileUpload"

src/components/UploadFiles

代码如下:

import React, { Component } from "react";
import UploadService from "../services/UploadFilesService"

export default class UploadImages extends Component {
  constructor(props) {
    super(props);
  }

  render() {

    return (
      <div>

        
      </div>
    );
  }
}

接着我们在 constructor() 方法内部定义状态, 代码如下

export default class UploadImages extends Component {
  constructor(props) {
    super(props);
    ...
    this.state = {
      selectedFiles: undefined,
      previewImages: [],
      progressInfos: [],
      message: [],
      imageInfos: [],
    };
  }
}

状态定义好后,我们在添加一个选择图片的方法 selectFiles(),并添加 <input type="file" >,

export default class UploadImages extends Component {
  ...
  selectFiles(event) {
    let images = [];
    for (let i = 0; i < event.target.files.length; i++) {
      images.push(URL.createObjectURL(event.target.files[i]))
    }
    this.setState({
      progressInfos: [],
      message: [],
      selectedFiles: event.target.files,
      previewImages: images
    });
  }
}

我们使用 URL.createObjectURL() 用来获取图片预览 URL 并将其放入 previewImages 数组中, URL.createObjectURL()方法会创建一个 DOMString ,其中包含一个表示参数中提供的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定,

接着我们定义文件上传方法 upload

export default class UploadImages extends Component {
  ...
  upload(idx, file) {
    let _progressInfos = [...this.state.progressInfos];
    UploadService.upload(file, (event) => {
      _progressInfos[idx].percentage = Math.round((100 * event.loaded) / event.total);
      this.setState({
        progressInfos: _progressInfos,
      });
    })
      .then(() => {
        this.setState((prev) => {
        let nextMessage = [...prev.message, `${file.name} 上传成功!`];
          return {
            message: nextMessage
          };
        });
        return UploadService.getFiles();
      })
      .then((files) => {
        this.setState({
          imageInfos: files.data,
        });
      })
      .catch(() => {
        _progressInfos[idx].percentage = 0;
        this.setState((prev) => {
          let nextMessage = [...prev.message, "Could not upload the image: " + file.name];
          return {
            progressInfos: _progressInfos,
            message: nextMessage
          };
        });
      });
  }
}

这里图片的上传进度根据 event.loaded 和计算 event.total,图片上传完毕后,我们调用 UploadService.getFiles(),来获取服务器上图片信息来展示,另外,我们要需要在 componentDidMount 钩子函数中调用,以便在初始状态时获取数据

export default class UploadImages extends Component {
  ...
  componentDidMount() {
    UploadService.getFiles().then((response) => {
      this.setState({
        imageInfos: response.data,
      });
    });
  }
}

这里我们将界面的上传的 UI 渲染代码添加上

 <div>
        <div className="row">
          <div className="col-8">
            <label className="btn btn-default p-0">
              <input type="file" accept="image/*" onChange={this.selectFiles} />
            </label>
          </div>

          <div className="col-4">
            <button
              className="btn btn-success btn-sm"
              disabled={!selectedFiles}
              onClick={this.uploadImages}
            >
              上传
            </button>
          </div>
        </div>

        {progressInfos &&
          progressInfos.map((progressInfo, index) => (
            <div className="mb-2" key={index}>
              <span>{progressInfo.fileName}</span>
              <div className="progress">
                <div
                  className="progress-bar progress-bar-info"
                  role="progressbar"
                  aria-valuenow={progressInfo.percentage}
                  aria-valuemin="0"
                  aria-valuemax="100"
                  style={{ width: progressInfo.percentage + "%" }}
                >
                  {progressInfo.percentage}%
                </div>
              </div>
            </div>
          ))}

        {previewImages && (
          <div>
            {previewImages.map((img, i) => {
              return <img className="preview" src={img} alt={"image-" + i}  key={i}/>;
            })}
          </div>
        )}

        {message.length > 0 && (
          <div className="alert alert-secondary mt-2" role="alert">
            <ul>
              {message.map((item, i) => {
                return <li key={i}>{item}</li>;
              })}
            </ul>
          </div>
        )}

        <div className="card mt-3">
          <div className="card-header">图片列表</div>
          <ul className="list-group list-group-flush">
            {imageInfos &&
              imageInfos.map((img, index) => (
                <li className="list-group-item " key={index}>
                  <img src={img.url} alt={img.name} height="80px" />
                  <span className="ml-10"><a href={img.url} target="_blank">{img.name}</a></span>
                </li>
              ))}
          </ul>
        </div>
      </div>

在上面的代码中我们使用了 Boostrap 的进度条, 具体可查看 Bootstrap 文档。

将图片上传组件添加到App组件

打开 App.js 文件,将组件 UploadImages 引入并添加

import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";

import UploadImages from "./components/UploadFiles.js"

function App() {
  return (
    <div className="container">
      <p>图片上传</p>
      <div className="content">
        <UploadImages />
      </div>
    </div>
  );
}

export default App;

上传图片配置端口

考虑到大多数的 HTTP Server 服务器使用 CORS 配置,我们这里在根目录下新建一个 .env 的文件,添加如下内容

PORT=8081

项目运行

到这里我们可以运行下前端项目了,使用命令 npm start,浏览器地址栏输入 http://localhost:8081/, ok 项目正常运行

截屏2022-05-27 下午11.07.31.png

前端项目到这里,大部分已经完成了,接下来我们在下一篇文章中搭建后端部分,连通前后端。