美文网首页JavaScript 进阶营JS进步之路Vue
Vue + Element UI + Koa 实现多图片+数据上

Vue + Element UI + Koa 实现多图片+数据上

作者: Qibing_Fang | 来源:发表于2019-06-10 11:48 被阅读1次

    一、写作背景

    最近在用Vue写一个仿京东、淘宝的电商项目过程中踩了一个大坑 ---- 多图片上传 + 保存

    二、问题描述

    • 电商项目其中一个较为核心的功能当然就是商品的添加了,而添加商品势必涉及到图片的上传。
    • 而一种商品很明显不止一张图片,其实严格来说大概要15张,因为其中不光要有缩略图+正常图,还有一个放大镜的功能要实现,当然,我们这里暂时不考虑性能的问题,只要求5张图片
    • 不过,即使是5张,也涉及到了多图片上传的问题。虽然element UI本身支持多图片上传,但是其内部机制是每张图片发送一个http请求的,这不是我们想要的
    • 这个问题卡了我不少时间,期间找了不少资料,然并软
    • 对于一个上线的项目来说,我觉得图片应该是有图片服务器的,如果仔细看一下就会发现京东、淘宝的图片地址都是网络地址,直接从服务器请求过来的,这种情况其实就很简单,不过对我们初学者练手来说,这不切实际,毕竟租服务器是要钱的嘛

    三、项目介绍及使用的工具

    • 这个项目采用的是前后端分离的方式写的
    • 前端使用的是Vue.js,用了vue-cli 3.x
    • 后台管理同样使用的是Vue
    • 服务端使用的是Node.js,采用了我比较熟悉的Koa框架(跟Express差不多,开发团队都一样)
    • 跨域问题的解决方法使用的是Vue提供的方法,配置项目目录下的vue.config.js文件即可,如果没有就新建一个,具体配置这里就不一一赘述了,有需要的话可以找我
    • 存储文件使用的是koa-multer中间件
    • HTTP请求: axios
    • 图片上传使用的是:Element UI uploads组件

    Element UI 中文站点

    https://element.eleme.cn/#/zh-CN/component/layout

    Element UI Github

    https://github.com/ElemeFE/element

    四、多图片上传的流程

    • 1、使用Element UI 的uploads组件获取需要上传的图片(别忘了配置支持多文件上传的属性)
    • 2、使用HTML5提供的FormData将文件添加进去
    • 3、使用axios发送http请求,并将文件数据发送到服务端
    • 4、服务端接收数据,并使用koa-multer将文件存储到本地
    • 5、获取图片的路径,将路径存到数据库,需要的时候提取出来返回到前端
    • 6、前端根据后端返回的图片路径再进行合适的处理将图片展示到页面

    5、前端代码及解析

    <template >
        <div id="goods-add">
            <el-form :model="goodinfo" ref="goodinfo" label-width="100px" class="demo-ruleForm">
                <el-form-item label="名字">
                    <el-input v-model="goodinfo.name"></el-input>
                </el-form-item>
    
                <el-form-item label="价格">
                    <el-input v-model="goodinfo.price"></el-input>
                </el-form-item>
    
                <el-form-item label="描述">
                    <el-input v-model="goodinfo.description"></el-input>
                </el-form-item>
    
                <el-form-item label="品牌">
                    <el-input v-model="goodinfo.brand"></el-input>
                </el-form-item>
    
                <el-form-item label="标签">
                    <el-input v-model="goodinfo.label" placeholder="每个标签使用 分开"></el-input>
                </el-form-item>
    
                <div class="img-upload">
                    <el-upload
                        action="#"  // 上传地址,这里我们手动上传,所以不需要填写地址
                        :limit="5"   // 限制上传文件最大数量为5
                        ref="upload"  //标记,我觉得相当于id,可用来选取元素
                        :multiple="true"   // 开启多文件上传
                        :auto-upload="false"   //关闭自动上传
                        :file-list="fileList"  // 上传文件列表
                        list-type="picture-card"> // 上传文件的展示形式,这个是卡片
                        <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
                        <div slot="tip" class="el-upload__tip">上传图片大小不超过500kb</div>
                    </el-upload>
                </div>
    
                <el-form-item>
                    <el-button type="primary" @click="submitUpload">立即创建</el-button>
                    <el-button @click="resetForm('goodinfo')">重置</el-button>
                </el-form-item>
    
            </el-form>
        </div>
    </template>
    
    
    <script>
    import axios from 'axios'
    
    export default {
        name: 'goods-add',
        methods: {
            submitUpload() {
                // 获取到 上传的所有文件,它是一个数组
                const fileArray = this.$refs.upload.uploadFiles;
                // 实例化FormData对象
                const fd = new FormData();
                // 遍历文件数组,将所有文件存入fd中
                for(let i = 0; i < fileArray.length; i++) {
                    // 在这里数组每一项的.raw才是你需要的文件,有疑惑的可以打印到控制台看一下就清楚了
                    fd.append('avatar', fileArray[i].raw);
                }
                // 发送HTTP请求,发送数据
                axios({
                    url: '/api/view/add-good',
                    method: 'post',
                    data: fd,
                }).then(res => {
                    console.log(res.data);
                })
            }
        }
    }
    </script>
    

    六、后端Koa使用koa-multer接收文件并保存

    6.1 koa-multer的安装与配置

    • 安装: npm install --save koa-multer
    • 配置:
    const multer = require('koa-multer');
    const storage = multer.diskStorage({
        destination (req, file, cb) {
            // 设置文件的存储目录,需提前创建
            cb(null, '../mall-view/src/assets/img')
        },
        filename (req, file, cb) {
            // 设置 文件名
            const name = file.originalname;
            // 设置文件的后缀名,
            //我这里取的是上传文件的originalname属性的后四位,
            // 即: .png,.jpg等,这样就需要上传文件的后缀名为3位
            const extension = name.substring(name.length - 4);
            cb(null, 'img-' + Date.now() + extension);
        }
    })
    
    const upload = multer({ storage: storage })
    

    6.2 使用

    router.post('/view/add-good', upload.array('avatar', 5), async (ctx) => {
       const files = ctx.req.files; //上传过来的文件
       ctx.body = {msg: '添加成功'};  //返回数据
    })
    
    • 上面代码中的upload.array('avatar', 5)就是koa-multer的使用了,程序进行到这里,就会将你上传的图片保存到本地了,
    • 其中'avatar'就是前端fd.append('avatar', fileArray[i].raw);中的'avatar',这个字段名换了,服务端的就也要换
    • 而数字5则是用来限制文件个数 的

    7、携带form表单中的数据一起上传

    针对这个需求,element UI 提供了data属性,用于上传携带的数据,但是我们用不到,因为我们的数据是自己发送http请求自己上传的。

    这个问题也困扰了我不少时间,其原因可能是我一开始就想岔了,

    7.1 当时我有两个想法:

    它们的依据都是这个:
    const files = ctx.req.files; //上传过来的文件
    const data = ctx.request.body; // 上传的数据
    当发送的是文件时, files !== undefined , data === {};
    当发送的是数据时, files === undefined , data !== {}

    • 1、发送两次请求,一次传文件,一次传数据,后端通过判断files的值是否为undefined,是的话说明本次请求发送的是数据,不是的话说明发送的是图片文件,定然后义变量将对应的数据接收,然后一起存入数据库中即可

    很明显这个方案是行不通的,因为每次发送http请求,此段代码都会运行一次,根本不可能同时获取到所有的数据

    • 2、改进后的方案:知道了问题所在的话解决就很容易了,当时我就采用了一个特别笨的办法 ---- 一次添加数据、一次更新数据,第二次请求更新数据的时候还得先获取到该数据的id,

    当然,方法虽然很笨,但是是能解决问题的,即使这很不可取,但是也不失为一种解决方案

    7.2 更加优雅的做法

    上面那种方法很明显不好,太浪费资源了,而且还很慢,一旦项目大一点就炸了,所幸我后来在做搜索功能的时候想到了一种更好的办法,这种办法其实我之前在写论坛项目的时候经常用,但是不知道为什么这次没想到,失败啊失败
    他就是:通过params发送数据,axios支持这个

    所以,改进后的代码如下:
    前端:

    submitUpload() {
                const session = this.$session.getAll();
                const boss = session.userinfo;
                const goodinfo = this.goodinfo;
                axios({   // 之所以要写这个请求,是因为我需要获取添加商品的商家信息
                    method: 'post',
                    url: '/api/view/getstore',
                    data: { boss_id: boss.boss_id}
                }).then(res => {
                    if(res.status === 200) {
                        const store_id = res.data.id;
                        const store_name = res.data.name;
                        const boss_id = boss.boss_id;
                        const boss_name = boss.username;
                        const name = goodinfo.name;
                        const new_price = goodinfo.price;
                        const description = goodinfo.description;
                        const brand = goodinfo.brand;
                        const label = goodinfo.label;
                        const data = {
                            store_id: store_id,
                            store_name: store_name,
                            boss_id: boss_id,
                            boss_name: boss_name,
                            name: name,
                            new_price: new_price,
                            description: description,
                            brand: brand,
                            label: label
                        };
                        const fileArray = this.$refs.upload.uploadFiles;
                        const fd = new FormData();
                        for(let i = 0; i < fileArray.length; i++) {
                            fd.append('avatar', fileArray[i].raw);
                        }
                        axios({
                            url: '/api/view/add-good',
                            method: 'post',
                            data: fd,
                            params: data // 将数据放在就可以上传到服务端
                        }).then(res => {
                            console.log(res.data);
    
                        })
                    }
                })
            },
    

    后端:

    router.post('/view/add-good', upload.array('avatar', 5), async (ctx) => {
        const files = ctx.req.files; //上传过来的文件
        // 服务端通过ctx.query 可以获得前端axios中的params里的数据
        const data = ctx.query;  // 上传的数据
    
        const img_1 = files[0].path;
        const img_2 = files[1].path;
        const img_3 = files[2].path;
        const img_4 = files[3].path;
        const img_5 = files[4].path;
        const store_id = data.store_id;
        const store_name = data.store_name;
        const boss_id = data.boss_id;
        const boss_name = data.boss_name;
        const name = data.name;
        const new_price = data.new_price;
        const description = data.description;
        const brand = data.brand;
        const label = data.label;
    
    
        const data1 = [store_id, store_name, boss_id, boss_name, name, new_price, description, brand, img_1, img_2, img_3, img_4, img_5, label];
        await editGood.addGood(data1);
    
        ctx.body = {msg: '添加成功'};
    })
    

    八、结束语

    • 以上就是此次的全部内容了,希望对你有所帮助,如有错误,欢迎指正,我会及时修改的 _

    相关文章

      网友评论

        本文标题:Vue + Element UI + Koa 实现多图片+数据上

        本文链接:https://www.haomeiwen.com/subject/wzuaxctx.html