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

目录

技术栈

[1. 项目搭建前期工作(不算太详细)](#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

sql 复制代码
pnpm create vite

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

配置+安装需要用到的库

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

安装需要用到的库

sql 复制代码
pnpm i vue-router
pnpm i element-plus
pnpm install @element-plus/icons-vue
pnpm i axios

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

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

javascript 复制代码
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项目

javascript 复制代码
npx express-generator

需要用到的依赖

javascript 复制代码
npm i cors
npm i formidanle@2.1.2

在app.js文件中配置跨域

javascript 复制代码
//配置跨域
var cors = require('cors')

app.use(cors())

启动项目

javascript 复制代码
npm start

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

目前路由文件是这样的

javascript 复制代码
//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文件

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

javascript 复制代码
<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用于控制上传图片时的不同状态:选择图片->上传中->上传成功

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

html 复制代码
<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博客,其他的就很简单了,代码基本能看懂。

javascript 复制代码
<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

javascript 复制代码
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文件夹中。

javascript 复制代码
//放置上传图片的处理函数
//导入处理文件上传的包
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.测试效果

上传中

上传成功

后端文件夹中

结语

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

相关推荐
余道各努力,千里自同风17 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave24 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟26 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
chusheng18401 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
游走于计算机中摆烂的1 小时前
启动前后端分离项目笔记
java·vue.js·笔记
怕冷的火焰(~杰)2 小时前
Node基本使用
node.js
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
码蜂窝编程官方3 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游