Vue + Element UI + Koa 实现多图片+数据上传并保存图片到本地

一、写作背景

最近在用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、前端代码及解析





六、后端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 实现多图片+数据上传并保存图片到本地)