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

<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: '添加成功'};
})

八、结束语

  • 以上就是此次的全部内容了,希望对你有所帮助,如有错误,欢迎指正,我会及时修改的 _
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,140评论 0 1
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    柴东啊阅读 15,848评论 2 140
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    小姜先森o0O阅读 9,395评论 0 72
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    你猜_3214阅读 11,035评论 0 118
  • 一件绝美的白色婚纱挂在玻璃展示柜里,整个房间阔气干净,一眼望去只有这件婚纱,孤独又傲然地立在那里。 门“吱”的一声...
    黍小藜阅读 861评论 0 6