Vue3 + Nodejs 实战 ,文件上传项目--实现图片上传-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

技术栈

1. 项目搭建前期工作(不算太详细)

前端

 后端

2.配置基本的路由和静态页面

 3.完成图片上传的页面imageUp

静态页面搭建

 上传图片的接口

 js逻辑

4.编写上传图片的接口

5.测试效果

 结语


博客主页専心_前端,javascript,mysql-CSDN博客

 系列专栏vue3+nodejs 实战--文件上传

 前端代码仓库jiangjunjie666/my-upload: vue3+nodejs 上传文件的项目用于学习 (github.com)

 后端代码仓库jiangjunjie666/my-upload-server: nodejs上传文件的后端 (github.com)

 欢迎关注

本系列记录vue3(前端)+nodejs(后端) 实现一个文件上传项目目前只完成了图片的上传后续会陆续完成单文件上传多文件上传大文件分片上传拖拽上传等功能欢迎关注。

技术栈

前端Vue3 Vue-router axios element-plus...

后端nodejs express...

1. 项目搭建前期工作(不算太详细)

前端

我使用的是vite创建的vue项目包管理器工具为pnpm

pnpm create vite

创建好项目后安装依赖就可启动项目了

配置+安装需要用到的库

配置文件路径别名在vite.config.js文件中

安装需要用到的库

pnpm i vue-router
pnpm i element-plus
pnpm install @element-plus/icons-vue
pnpm i axios

进行基础配置建好对应的文件夹

导入Element-Plus (在main.js文件中)

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
//引入样式
import 'element-plus/dist/index.css'
import router from '@/router/index.js'

const app = createApp(App)
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.use(ElementPlus, {
  locale: zhCn
})
app.use(router)
app.mount('#app')

 后端

使用express框架快速搭建出node项目

npx express-generator

需要用到的依赖

npm i cors
npm i formidanle@2.1.2

在app.js文件中配置跨域

//配置跨域
var cors = require('cors')

app.use(cors())

启动项目

npm start

2.配置基本的路由和静态页面

目前路由文件是这样的

//vue-router
// import Vue from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      //重定向至主页
      redirect: '/home'
    },
    {
      path: '/home',
      component: () => import('../views/home/index.vue'),
      name: 'home',
      redirect: '/home/imageUp',
      meta: {
        title: '首页'
      },
      children: [
        {
          path: '/home/imageUp',
          component: () => import('../views//home/imageUp/index.vue'),
          name: 'imageUp',
          meta: {
            title: '图片上传'
          }
        },
        {
          path: '/home/videoUp',
          component: () => import('../views/home/videoUp/index.vue'),
          name: 'videoUp',
          meta: {
            title: '视频上传'
          }
        },
        {
          path: '/home/fileUp',
          component: () => import('@/views/home/fileUp/index.vue'),
          name: 'fileUp',
          meta: {
            title: '文件上传'
          }
        }
      ]
    }
  ]
})

export default router

目前就这几个页面

在App.vue中使用路由占位

home主页中的index.vue文件

这其中除了静态页面的搭建外我使用了编程式路由跳转方式实现路由跳转后续可能会添加更多功能也可以使用其他的方式实现跳转不唯一。

<template>
  <div class="container">
    <div class="top">My upload</div>
    <div class="heart">
      <div class="left">
        <ul>
          <li :class="{ active: activeIndex == 1 }" @click="changeUp('/home/imageUp', 1)">
            <el-icon size="20"><PictureFilled /></el-icon>
            <p>图片上传</p>
          </li>
          <li :class="{ active: activeIndex == 2 }" @click="changeUp('/home/fileUp', 2)">
            <el-icon size="20"><FolderAdd /></el-icon>
            <p>文件上传</p>
          </li>
          <li :class="{ active: activeIndex == 3 }" @click="changeUp('/home/videoUp', 3)">
            <el-icon size="20"><VideoCamera /></el-icon>
            <p>视频上传</p>
          </li>
        </ul>
      </div>
      <div class="layout">
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
//引入路由
import { useRouter } from 'vue-router'

const $router = useRouter()
let activeIndex = ref(1)

//二级路由跳转函数
const changeUp = (path, index) => {
  //路由跳转
  $router.push(path)
  activeIndex.value = index
}
</script>

<style lang="scss" scoped>
.container {
  .top {
    width: 100vw;
    height: 100px;
    background-color: rgb(61, 221, 154);
    text-align: center;
    font-size: 30px;
    color: #fff;
    line-height: 100px;
  }
  .heart {
    width: 100vw;
    //高度减去100px
    height: calc(100vh - 100px);
    display: flex;
    .left {
      width: 350px;
      height: 100%;
      background-color: #fffcfc;
      border-right: 1px solid #ccc;
      // padding-left: 20px;
      ul {
        li {
          width: 100%;
          height: 40px;
          display: flex;
          align-items: center;
          padding-left: 20px;
          p {
            margin-left: 10px;
            font-size: 16px;
            line-height: 40px;
          }
        }
        //给li加个active
        .active {
          color: rgb(61, 221, 154);
        }
        //加hover
        li:hover {
          cursor: pointer;
          background-color: rgb(146, 236, 199);
          opacity: 0.8;
          color: black;
        }
      }
    }
    .layout {
      width: calc(100% - 350px);
      height: 100%;
      padding: 20px;
    }
  }
}
</style>

目前项目长这样

 3.完成图片上传的页面imageUp

静态页面搭建

act用于控制上传图片时的不同状态选择图片->上传中->上传成功

上传成功的图片会展示在下方的照片墙中

<template>
  <div class="box">
    <div class="add">
      <input type="file" ref="fileInputRef" style="display: none" @change="handleFileChange" />
      <el-icon size="100" color="#ccc" v-if="act == 1" @click="openFileInput"><Plus /></el-icon>
      <!-- loading效果 -->
      <div class="loading" v-if="act == 2"></div>
      <img :src="base64Img" alt="" v-if="act == 2" />
    </div>
  </div>
  <div class="imgList">
    <ul>
      <li v-for="item in imgList"><img :src="item" alt="" /></li>
    </ul>
  </div>
</template>

<style lang="scss" scoped>
.box {
  width: 350px;
  height: 350px;
  border: 2px dashed rgb(175, 171, 171);
  border-radius: 2em;
  display: flex;
  justify-content: center;
  align-items: center;
  .add {
    position: relative;
    .loading {
      width: 100px;
      height: 100px;
      position: absolute;
      top: 35%;
      left: 35%;
      border: 3px solid #302b2b;
      border-top-color: transparent;
      border-radius: 50%;
      animation: circle infinite 0.75s linear;
    }
    // 转转转动画
    @keyframes circle {
      0% {
        transform: rotate(0);
      }
      100% {
        transform: rotate(360deg);
      }
    }
    img {
      width: 350px;
      height: 350px;
      z-index: -1;
      // 增加点模糊透明度
      opacity: 0.5;
    }
  }
  .add:hover {
    cursor: pointer;
  }
}
.imgList {
  width: 60%;
  // background-color: pink;
  margin-top: 30px;
  ul {
    border: 1px solid #ccc;
    border-radius: 20px;
    display: flex;
    flex-wrap: wrap;
    padding-left: 20px;
    li {
      width: 200px;
      height: 200px;
      margin: 5px 6px;
      // border: 1px solid pink;
      img {
        width: 100%;
        border-radius: 20px;
        height: 100%;
      }
    }
  }
}
</style>

 上传图片的接口

 js逻辑

我想实现的是有加载中的一个效果但是单图片上传的速度所以我使用了定时器来看到这个上传的效果其中还没完成上传的图片能显示加载出来主要是将上传的图片转为了base64格式临时显示在页面加载上因为base64加载要比服务器上传快不懂图片转base64的可以看这个前端图片转base64并使用canvas对图片进行压缩_图片base64压缩-CSDN博客其他的就很简单了代码基本能看懂。

 

<script setup>
import { ref } from 'vue'
import { reqUploadImg } from '@/api/data.js'
import { ElMessage } from 'element-plus'
let act = ref(1)
const fileInputRef = ref(null)
let selectedFile = ref(null)
let base64Img = ref('')
let imgList = ref([])
//上传函数
const openFileInput = () => {
  // 点击图标时触发文件选择框
  fileInputRef.value.click()
}

const handleFileChange = async (event) => {
  // 处理文件选择事件
  act.value = 2
  selectedFile.value = event.target.files[0]
  if (!selectedFile.value) {
    return
  }

  // 将图片转为base64显示loading状态
  const reader = new FileReader()
  reader.onload = (e) => {
    const img = new Image()
    img.src = e.target.result
    img.onload = () => {
      // 图片加载完成
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')

      const maxWidth = 300 // 设置最大宽度
      const maxHeight = 300 // 设置最大高度
      let width = img.width
      let height = img.height

      // 如果图片尺寸大于最大宽度或最大高度则按比例缩放图片
      if (width > maxWidth || height > maxHeight) {
        const ratio = Math.min(maxWidth / width, maxHeight / height)
        width *= ratio
        height *= ratio
      }

      canvas.width = width
      canvas.height = height

      ctx.drawImage(img, 0, 0, width, height)

      const compressedDataUrl = canvas.toDataURL('image/jpeg') // 压缩图片质量为0.8
      console.log(compressedDataUrl)
      base64Img.value = compressedDataUrl
    }
  }
  reader.readAsDataURL(selectedFile.value) //调用生成base64

  // 创建一个FormData对象来包装文件
  const formData = new FormData()
  formData.append('file', selectedFile.value)

  // 使用上传图片的接口函数发送请求
  let res = await reqUploadImg(formData)
  if (res.code !== 200) {
    ElMessage({
      type: 'error',
      message: '上传失败'
    })
    act.value = 1
    return
  }
  //成功
  //开个定时器(为了看到上传的加载效果)
  else {
    let timer = setInterval(() => {
      act.value = 1
      clearInterval(timer)
      imgList.value.push(res.imgUrl)
      ElMessage({
        type: 'success',
        message: '上传成功'
      })
    }, 1000)
  }
}
</script>

到这里前端的功能基本完成了

4.编写上传图片的接口

先建好文件夹

路由images.js

var express = require('express')
var router = express.Router()

const handler = require('./image_handler')
//挂载路由
router.post('/imageUpload', handler.imageUp)

module.exports = router

接口函数

这里使用的包是formidable下载的版本是2.1.2 如果版本不同可能代码会有所差异

这里限制了上传的图片类型和图片大小并且将图片上传至了public/images文件夹中。

//放置上传图片的处理函数
//导入处理文件上传的包
const formidable = require('formidable')
const path = require('path')
exports.imageUp = (req, res, next) => {
  const form = formidable({
    multiples: true,
    uploadDir: path.join(__dirname, '../../public/images'),
    keepExtensions: true
  })
  form.parse(req, (err, fields, files) => {
    if (err) {
      next(err)
      return
    }
    console.log(files)
    //切割出上传的文件的后缀名
    let ext = files.file.mimetype.split('/')[1]
    //计算出图片文件大小
    let size = (files.file.size / 1024 / 1024).toFixed(2)
    if ((ext == 'png' || ext == 'jpg' || ext == 'jpeg') && size < 2) {
      let url = 'http://127.0.0.1:3000/images/' + files.file.newFilename
      res.send({
        code: 200,
        msg: '上传成功',
        imgUrl: url
      })
    } else {
      res.send({
        code: 400,
        msg: '只能上传png、jpg、jpeg格式的图片或图片过大'
      })
      return
    }
  })
}

5.测试效果

上传中

上传成功

 

 后端文件夹中

 结语

如果没有接触过文件上传或者想尝试开发一下文件上传项目的可以交流学习一下后续会陆续更新更多的功能如有想法可在评论区交流或私信感谢关注

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: NodeJSvue