目录
[1. 项目搭建前期工作(不算太详细)](#1. 项目搭建前期工作(不算太详细))
博客主页:専心_前端,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.测试效果
上传中
上传成功
后端文件夹中
结语
如果没有接触过文件上传或者想尝试开发一下文件上传项目的可以交流学习一下,后续会陆续更新更多的功能,如有想法可在评论区交流或私信,感谢关注!!!