今天来造一个轮子,vue的图片上传组件,虽说ElementUI已经有了,但是不满足我的需求,所以不得已造了一个
个人博客后台每篇博文需要上传一张主图
我的需求是在编辑文章的时候获取到存在的图片,可以将其删除重新上传(就是修改文章主图)
但是ElementUI只可以上传图片,然后点击删除按钮删除图片,不能设置初始图片
👉👉👉 前端Nuxt.js 后端Node.js(express) 数据库MongoDB
博客地址 👉👉👉 前端靓仔
首先来展示前端封装的组件
注意:组件中使用到了 ElementUI 的 icon 图标
<template>
<div class="upload">
<div class="imgList">
<!-- 初始化图片 -->
<div class="img" v-if="imgInit.length>0">
<img :src="imgInit" alt="">
<p>
<span class="el-icon-search" @click="previewImageInit(imgInit)"></span>
<span class="el-icon-delete-solid" @click="deleteImageInit(imgInit)"></span>
</p>
</div>
<!-- 上传图片列表 -->
<div class="img" v-for="(item,index) in imgList" :key="index">
<img :src="host + item" alt="">
<p>
<span class="el-icon-search" @click="previewImage(item)"></span>
<span class="el-icon-delete-solid" @click="deleteImage(item)"></span>
</p>
</div>
<!-- 上传按钮 -->
<div class="upload" @click="handleClick">
<span class="el-icon-plus"></span>
<input type="file" ref="file" @change="changeImage" :multiple="multiple" :accept="accept">
</div>
</div>
</div>
</template>
props:{
multiple:{//是否可多选
type:Boolean,
default:false
},
accept:{//上传文件类型
type:String,
default:'.jpg,.png,.jpeg,.gif'
},
imgList:{//上传图片列表,具体可以上传多少图片由后端接口决定,下面写接口再说
type:Array,
default:[]
},
imgInit:{//初始化图片,因为我主图只有一张,所以没有考虑多个,如果想写多个可以改成数组试试
type:String,
default:''
},
host:{//图片上传地址 如:http://localhost:8000/ 预览图片需要使用
type:String
}
},
methods:{
// 预览初始化图片
previewImageInit(){
//服务端返回的完整图片路径,父组件通过传递给子组件显示在上传按钮旁
//父组件无法点击子组件的预览图片按钮,所以通过子组件点击传递给父组件
//父组件监听previewImageInit方法来接收初始图片,用来显示在预览区域
this.$emit('previewImageInit',this.imgInit)
},
// 删除初始化图片
deleteImageInit(){
//原理同上,父组件同样无法点击子组件的删除按钮
this.$emit('deleteImageInit',this.imgInit)
},
// 预览上传图片
previewImage(item){
//父组件同样无法点击子组件的预览按钮
//item为上传图片之后返回的图片路径,只存在相对路径,要想页面显示,需加上图片上传地址
this.$emit('previewImage',this.host + item)
},
// 删除上传图片
deleteImage(item){
//父组件同样无法点击子组件的预览按钮
//删除图片是从相对目录中删除,所以不需要加上图片上传地址
this.$emit('deleteImage',item)
},
// 上传图片
handleClick(){
//使用的input:file上传图片,但样式较丑,所以自己写了个上传图片按钮,点击按钮触发input:file来上传
this.$refs.file.click();
},
// 当图片改变之后
changeImage(e){
//当图片选中图片之后,父组件通过upload方法可以获取到图片信息
this.$emit('upload',e.target.files)
}
}
/*使用的scss*/
/*样式就不介绍了,有些重复的没有做优化*/
.upload{
width:100%;
padding: 10px 0;
.imgList{
display: flex;
flex-wrap: wrap;
.img{
margin:5px;
width: 140px;
height: 140px;
border-radius: 10px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
p{
font-size: 20px;
z-index: 2;
opacity: 0;
span{
margin: 0 10px;
cursor: pointer;
}
}
img{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid #ccc;
border-radius: 10px;
opacity: 1;
transition: .5s;
}
&:hover{
img{
opacity: 0.2;
}
p{
opacity: 1;
}
}
}
}
.upload{
box-sizing: border-box;
margin:5px;
width: 140px;
height: 140px;
border-radius: 10px;
background-color: #f5f5f5;
border: 1px dashed #ccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: #8c7e7e;
cursor: pointer;
input{
display: none;
}
}
}
接下来展示一下父组件
<upload
@previewImage="previewImage" <!--监听子组件预览上传图片事件-->
@deleteImage="deleteImage" <!--监听子组件删除上传图片事件-->
@previewImageInit="previewImageInit" <!--监听子组件预览初始图片事件-->
@deleteImageInit="deleteImageInit" <!--监听子组件删除初始图片事件-->
@upload="upload" <!--监听input:file改变事件-->
:imgList="imgList" <!--上传图片列表-->
:imgInit="artical_pic" <!--初始图片是个服务器返回的图片完整url,例:http://localhost:8000/upload/img/01.jpg-->
:host="host" <!--图片上传地址-->
></upload>
<!--以下为ElementUI的组件,用来展示预览图片-->
<el-dialog :visible.sync="dialogVisible"><img width="100%" :src="dialogImageUrl"></el-dialog>
data(){
return{
artical_pic:'',//初始化图片
dialogImageUrl: '',//预览图片url
dialogVisible: false,//是否显示预览窗口
imgList:[],//上传图片列表
host:BASE_URL,//图片存储地址
}
},
methods:{
// 上传图片预览
previewImage(item){
this.dialogImageUrl = item;
this.dialogVisible = true;
},
// 删除上传图片
deleteImage(item){
//调用接口删除图片
//遍历imgList,删除item
},
// 初始化图片预览
previewImageInit(item){
this.dialogImageUrl = item;
this.dialogVisible = true;
},
// 删除初始化图片
deleteImageInit(){
this.artical_pic = ''
this.updateArticle();//这个方法是用来更新博文的,同时也会传入新的图片
},
// 图片上传
upload(fileList){
let that = this;
let formData = new FormData();
formData.append('img',fileList[0])//只上传一张图片,注意这里的'img'一定要和后端的一致,待会看接口的时候再说
//上传多张图片
//具体上传多少张图片由后端接口决定
// fileList.forEach(item => {
// data.append('img',item)
// })
//我用的vuex中的actions方法来调用上传接口,传入formData
this.$store.dispatch('uploadArticleImg',formData).then(res => {
let data = res.data.data;
that.imgList.push(data.url)//将返回的图片url添加到上传图片列表
}).catch(err => {
console.log(err)
})
}
}
这里最后来展示一下后端接口(Node.js)
最开始需要先配置静态资源文件,要不然图片会访问不到
const express = require('express');
const app = express();
app.use(express.static(__dirname + '/'));//配置静态资源目录
//使用的是multer中间件
//所以最开始需要先npm i multer -S来安装
const fs = require('fs');//fs模块
const multer = require("multer");//处理图片
// 设置图片存储路径
const storage = multer.diskStorage({
// 设置存储路径
destination: function(req, file, cb) {
cb(null, './upload/article');
},
// 设置存储的图片名
filename: function(req, file, cb) {
cb(null, `${Date.now()}-${file.originalname}`)
}
})
// 添加配置文件到muler对象。
const upload = multer({ storage: storage });
//上传文章主图
//注意:这里的'img'要与父组件中upload方法中formData中的img一致,1指的是上传图片的数量
router.post('/upload', upload.array('img', 1), function (req, res) {
// 读取上传的图片信息
let files = req.files;//files为上传的图片数组
// 设置返回结果
let result = {};
if(!files[0]) {
result.code = 1;
result.errMsg = '上传失败';
} else {
result.code = 0;
result.data = {
url: files[0].path
}
result.errMsg = '上传成功';
}
res.send(result);
});
// 删除文章主图
router.get('/delete/img',function (req, res) {
let {name} = req.query;
fs.unlink(`./${name}`,function (err) {
if(err) throw err;
res.send({code:0,msg:'删除成功'})
})
});
最后来看一下效果