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.rhkb.cn/news/156448.html

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

相关文章

springboot json在线转换为实体类

json字符串映射到一个实体类。 这里有一个在线转换工具 http://www.bejson.com/json2javapojo/new/ 截图如下&#xff1a;

【总结】kubernates 插件工具总结

在此记录工作中用到的关于 kubernates 的插件小工具&#xff0c;以防以后忘记 1、能显示 kubernates 所处上下文的插件 kube-ps1 github 地址&#xff1a; https://github.com/jonmosco/kube-ps1 效果 2、能方便切换 kubernates 上下文的插件 kubecm github 地址&#xff1…

Excel 规范录入数据

文章目录 录入日期录入百分比 快捷键&#xff1a; tab&#xff1a;向右切换单元格 enter&#xff1a;向下切换行 shift tab&#xff1a;向左切换单元格 shiftenter&#xff1a;向上切换行 录入日期 输入今天的日期的快捷键&#xff1a;Ctrl ; 输入当时的时间的快捷键&a…

从零开始探索C语言(十一)----共用体和位域

文章目录 1. 共用体1.1 定义共用体1.2 访问共用体成员 2. 位域2.1 位域声明2.2 位域的定义和位域变量的说明2.3 位域的使用2.4 位域小结 1. 共用体 共用体是一种特殊的数据类型&#xff0c;允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体&#…

windows的最佳选项卡式窗口管理器软件TidyTabs

下载&#xff1a; https://jmj.cc/s/z1t3kt?pucodeS1wc https://download.csdn.net/download/mo3408/88420433 TidyTabs是一款Windows应用程序&#xff0c;它可以将多个打开的窗口整理成一个选项卡式的界面&#xff0c;使得用户可以更加方便地切换和管理不同的窗口。 Tidy…

实施运维02

一.网线制作 1.所需材料 网线&#xff0c;水晶头&#xff0c;网线钳&#xff0c;水晶头, 路由器或者网络测速仪 网线钳 网线制作标准 T568A标准&#xff08;交叉线&#xff09;&#xff1a;适用链接场合&#xff1a;电脑-电脑、交换机-交换机、集线器-集线器 接线顺序&…

gogs和drone如何配合使用

上篇介绍了drone和gogs安装方法&#xff0c;这次介绍这两个如何使用&#xff0c;此篇文章主要介绍在物理机上进行发布。 此处用到的java项目地址&#xff1a;https://gitee.com/huningfei/demo-test 一 配置gogs 1.1 在Gogs中配置指定仓库的”.drone.yml“文件 1.2 ssh-drone…

Linux-文件管理命令

绝对路径&#xff1a;从根目录开始描述的路径 pwd输入即为绝对路径&#xff0c; 开头一定是“/”&#xff0c;因为一定是从根目录开始走 相对路径&#xff1a;从当前路径开始描述的路径&#xff0c;开头不一定是“/”&#xff0c;因为不一定是从根目录开始走的 .:是当前目录 。…

【UnityUGUI】复合控件详解,你还记得多少

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;UI_…

【JVM】初步认识Java虚拟机

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、初识JVM1.1 什么是JVM1.2 JVM的功能…

ARM-day9作业

main.c: #include "uart.h"#include "key_it.h"int main(){char c;char *s;uart4_init(); //串口初始化//中断初始化key_it_config();key3_it_config();//完成GPIO相关初始化all_led_init();//风扇初始化fs_init();//蜂鸣器初始化fmq_init();while(1){…

SpringBoot-黑马程序员-学习笔记(四)

40.业务层Service的快速开发 1.写业务层接口并且继承IService类&#xff0c;泛型是对应的实体类 2.写实现类&#xff0c;除了和之前一样的实现Service类外&#xff0c;还要继承ServiceImpl类&#xff0c;泛型有2个&#xff0c;第一个是对应的Dao层&#xff0c;第2个是对应的实…

c++视觉---中值滤波处理

中值滤波&#xff08;Median Filter&#xff09;是一种常用的非线性平滑滤波方法&#xff0c;用于去除图像中的噪声。它不像线性滤波&#xff08;如均值滤波或高斯滤波&#xff09;那样使用权重来计算平均值或加权平均值&#xff0c;而是选择滤波窗口内的像素值中的中间值作为输…

LeetCode862 和至少为k的最短子数组

题目&#xff1a; 解析&#xff1a; 1、先构造前缀和数组 2、单调队列存放滑动窗口&#xff0c;目的求Sj-Si >k的情况下&#xff0c;窗口最小。 代码&#xff1a; class Solution {public int shortestSubarray(int[] nums, int k) {int n nums.length;long[] sums new …

读书笔记-《ON JAVA 中文版》-摘要26[第二十三章 注解]

文章目录 第二十三章 注解1. 基本语法1.1 基本语法1.2 定义注解1.3 元注解 2. 编写注解处理器2.1 编写注解处理器2.2 注解元素2.3 默认值限制 3. 使用javac处理注解4. 基于注解的单元测试5. 本章小结 第二十三章 注解 注解&#xff08;也被称为元数据&#xff09;为我们在代码…

Edge浏览器下载文件被保存为 .crdownload 文件的问题小记

问题 近期使用Edge浏览器下载文件时&#xff0c;文件都被保存为 .crdownload 格式的文件了&#xff0c;不确定从哪个版本开始的。除非下载未完成导致文件不完整&#xff0c;否则不会被保存为 .crdownload 格式的文件&#xff1b;实际上文件已完成了下载&#xff0c;且手工修改…

天锐绿盾加密软件——企业数据防泄密-CAD图纸、文档、源代码加密管理系统@德人合科技

天锐绿盾是一款专门为企业提供数据防泄密和文档加密管理的软件。该软件通过加密技术保护企业的核心数据&#xff0c;防止数据泄露和侵权行为&#xff0c;同时提供了全方位的文档加密管理系统&#xff0c;实现了对企业数据的安全保障和有效管理。 PC访问地址&#xff1a; isite…

基于保密信息学科平台系统

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 教师信息管理 学科动态管理 文献资源管理 征订目录管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步…

# Web server failed to start. Port 9793 was already in use

Web server failed to start. Port 9793 was already in use. 文章目录 Web server failed to start. Port 9793 was already in use.报错描述报错原因解决方法Spring Boot 修改默认端口号关闭占用某一端口号的进程关闭该进程 报错描述 Springboot项目启动控制台报错 Error st…

黄金票据与白银票据

文章目录 黄金票据与白银票据1. 背景2. 具体实现2.1 Kerberos协议认证流程 3. 黄金票据3.1 条件3.2 适用场景3.3 利用方式 4. 白银票据4.1 条件4.2 适用场景4.3 利用方式 5. 金票和银票的区别5.1 获取的权限不同5.2 认证流程不同5.3 加密方式不同 6. 经典面试题6.1 什么是黄金票…