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

目录

技术栈

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;//高度减去100pxheight: 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);}//加hoverli: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 = 2selectedFile.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.resultimg.onload = () => {// 图片加载完成const canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')const maxWidth = 300 // 设置最大宽度const maxHeight = 300 // 设置最大高度let width = img.widthlet height = img.height// 如果图片尺寸大于最大宽度或最大高度,则按比例缩放图片if (width > maxWidth || height > maxHeight) {const ratio = Math.min(maxWidth / width, maxHeight / height)width *= ratioheight *= ratio}canvas.width = widthcanvas.height = heightctx.drawImage(img, 0, 0, width, height)const compressedDataUrl = canvas.toDataURL('image/jpeg') // 压缩图片质量为0.8console.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 = 1return}//成功//开个定时器(为了看到上传的加载效果)else {let timer = setInterval(() => {act.value = 1clearInterval(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.newFilenameres.send({code: 200,msg: '上传成功',imgUrl: url})} else {res.send({code: 400,msg: '只能上传png、jpg、jpeg格式的图片或图片过大'})return}})
}

5.测试效果

上传中

上传成功

 

 后端文件夹中

 结语

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UTF-8的编码与解码

文章目录 一、什么是unicode二、什么是编码三、.unicode和utf-8编码是什么关系四、示例五、编码规则是什么 一、什么是unicode UNICDOE是一个字符集&#xff0c;它定义了世界上所有字符的唯一编号&#xff0c;每一个字符编号称为码点 详细参考- https://www.cnblogs.com/Illid…

【【萌新的SOC学习之GPIO之MIO控制LED实验程序设计】】

萌新的SOC学习之GPIO之MIO控制LED实验程序设计 如何设置完GPIO并且传递数据 我们先了解GPIO引脚的配置 每一个GPIO引脚都可以设置成输入输出 &#xff0c;只有GPIO8 7 只能作为输出 我们现在做一个例子 GPIO 的bank我们知道有4个 bank0 1 2 3 DIRM_0 就是第一个bank 需要写入…

如何压缩视频?视频压缩变小方法汇总

视频是我们日常生活中不可或缺的一部分&#xff0c;但视频文件往往会占用大量存储空间&#xff0c;这在传输和分享过程中可能成为一个瓶颈。 为了解决这一问题&#xff0c;我们可以通过压缩的方式减小视频大小&#xff0c;视频压缩是指在保证视频质量的前提下&#xff0c;通过…

【Debian系统】:安装debian系统之后,很多命令找不到,需要添加sudo之后才能使用,以下解决方法

项目场景&#xff1a; 问题描述 解决方案&#xff1a; 1.临时解决方案 2.永久解决方案 1.首先打开编辑&#xff1a; 2.打开之后最后一行添加代码&#xff1a; 3.最后运行一遍 .bashrc 4.已经可以了&#xff0c;可以试试reboot&#xff0c;重启一下机子 一点一滴才能成长 …

Mysql5.7大限将至升级Mysql 8.0过程记录(未完)

一、前言 时间很快&#xff0c;到2023年10月底&#xff0c;MySQL 5.7就到了它的EOL&#xff08;End of Life&#xff09;&#xff0c;届时将不会提供任何补丁&#xff0c;无法应对潜在的安全风险&#xff1b;是时候和 MySQL 5.7 说再见了&#xff01;&#xff01;&#xff01;&…

深入理解强化学习——序列决策(Sequential Decision Making)

分类目录&#xff1a;《深入理解联邦学习》总目录 在本文中我们将介绍序列决策&#xff08;Sequential Decision Making&#xff09;过程中的各个过程。 智能体与环境 强化学习研究的问题是智能体与环境交互的问题&#xff0c;下图左边的智能体一直在与下图右边的环境进行交互…

收银系统商品定价设计思考

一、背景 因为门店系统里商品总共也就几万款&#xff0c;一直以来都是根据条码由总部统一定价销售&#xff0c;现在有加盟店&#xff0c;各门店也有进行各自促销活动的需求&#xff0c;这就需要放开门店自主定价权&#xff0c;所以近段时间系统在商品定价上做了扩展。 二、商…

【tomcat、java】

java&#xff1a;maven配置 1.安装插件 <build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.1</version><configuration><port&…

基于web的酒店客房管理系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 会员信息管理 客房信息管理 收藏客房管理 用户入住管理 客房清扫管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施…

Android组件通信——ActivityGroup(二十五)

1. ActivityGroup 1.1 知识点 &#xff08;1&#xff09;了解ActivityGroup的作用&#xff1b; &#xff08;2&#xff09;使用ActivityGroup进行复杂标签菜单的实现&#xff1b; &#xff08;3&#xff09;使用PopupWindow组件实现弹出菜单组件开发&#xff1b; 1.2 具体…

2023全国大学生软件测试大赛开发者测试练习题99分答案(ScapegoatTree2023)

2023全国大学生软件测试大赛开发者测试练习题99分答案&#xff08;ScapegoatTree2023&#xff09; 题目详情题解代码&#xff08;直接全部复制到test类中即可&#xff09; 提示&#xff1a;该题只需要分支覆盖得分即可&#xff0c;不需要变异得分 题目详情 题解代码&#xff0…

C++——string

目录 STL STL六大组件 标准库中的string类 string类 string类常用接口 构造函数 下标遍历[] 迭代器 范围for push_back() append() insert() operator pop_back() erase() reserve resize clear c_str() substr() find() rfind() find_first_of getline str…