vue封装图片上传组件,nodejs实现图片上传

22 篇文章 1 订阅

今天来造一个轮子,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:'删除成功'})
    })    
});

 

最后来看一下效果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端薛小帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值