AntD框架的upload组件上传图片时遇到的一些坑
前言
本次做后台管理系统,采用的是 antd 框架。涉及到图片的上传,用的是antd的 组件。
前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 upload 组件的很多bug。下面来列举几个。
备注:本文写于2019-03-02,使用的 antd 版本是 3.13.6。
使用 antd 的 upload 组件做图片的上传
因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:
(1)上传中:
(2)上传成功:
(3)图片预览:
按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:
/* eslint-disable */ import { upload, icon, modal, form } from 'antd'; const formitem = form.item; class pictureswall extends purecomponent { state = { previewvisible: false, previewimage: '', imglist: [], }; handlechange = ({ file, filelist }) => { console.log(json.stringify(file)); // file 是当前正在上传的 单个 img console.log(json.stringify(filelist)); // filelist 是已上传的全部 img 列表 this.setstate({ imglist: filelist, }); }; handlecancel = () => this.setstate({ previewvisible: false }); handlepreview = file => { this.setstate({ previewimage: file.url || file.thumburl, previewvisible: true, }); }; // 参考链接:https://www.jianshu.com/p/f356f050b3c9 handlebeforeupload = file => { //限制图片 格式、size、分辨率 const isjpg = file.type === 'image/jpeg'; const isjpeg = file.type === 'image/jpeg'; const isgif = file.type === 'image/gif'; const ispng = file.type === 'image/png'; if (!(isjpg || isjpeg || isgif || ispng)) { modal.error({ title: '只能上传jpg 、jpeg 、gif、 png格式的图片~', }); return; } const islt2m = file.size / 1024 / 1024 < 2; if (!islt2m) { modal.error({ title: '超过2m限制 不允许上传~', }); return; } return (isjpg || isjpeg || isgif || ispng) && islt2m && this.checkimagewh(file); }; //返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传 checkimagewh(file) { let self = this; return new promise(function(resolve, reject) { let filereader = new filereader(); filereader.onload = e => { let src = e.target.result; const image = new image(); image.onload = function() { // 获取图片的宽高,并存放到file对象中 console.log('file width :' + this.width); console.log('file height :' + this.height); file.width = this.width; file.height = this.height; resolve(); }; image.onerror = reject; image.src = src; }; filereader.readasdataurl(file); }); } handlesubmit = e => { const { dispatch, form } = this.props; e.preventdefault(); form.validatefieldsandscroll((err, values) => {// values 是form表单里的参数 // 点击按钮后,将表单提交给后台 dispatch({ type: 'mymodel/submitformdata', payload: values, }); }); }; render() { const { previewvisible, previewimage, imglist } = this.state; // 从 state 中拿数据 const uploadbutton = ( <div> <icon type="plus" /> <div classname="ant-upload-text">upload</div> </div> ); return ( <div classname="clearfix"> <form onsubmit={this.handlesubmit} hiderequiredmark style={{ margintop: 8 }}> <formitem label="图片图片" {...formitemlayout}> {getfielddecorator('myimg')( <upload action="//jsonplaceholder.typicode.com/posts/" // 这个是接口请求 data={file => ({ // data里存放的是接口的请求参数 param1: myparam1, param2: myparam2, photocotent: file, // file 是当前正在上传的图片 photowidth: file.height, // 通过 handlebeforeupload 获取 图片的宽高 photoheight: file.width, })} listtype="picture-card" filelist={this.state.imglist} onpreview={this.handlepreview} // 点击图片缩略图,进行预览 beforeupload={this.handlebeforeupload} // 上传之前,对图片的格式做校验,并获取图片的宽高 onchange={this.handlechange} // 每次上传图片时,都会触发这个方法 > {this.state.imglist.length >= 9 ? null : uploadbutton} </upload> )} </formitem> </form> <modal visible={previewvisible} footer={null} oncancel={this.handlecancel}> <img alt="example" style={{ width: '100%' }} src={previewimage} /> </modal> </div> ); } } export default pictureswall;
上传后,点击图片预览,浏览器卡死的问题
依据上方的代码,通过 antd 的 upload 组件将图片上传成功后,点击图片的缩略图,理应可以在当前页面弹出 modal,预览图片。但实际的结果是,浏览器一定会卡死。
定位问题发现,原因竟然是:图片上传成功后, upload 会将其转为 base64编码。base64这个字符串太大了,点击图片预览的时候,浏览器在解析一大串字符串,然后就卡死了。详细过程描述如下。
上方代码中,我们可以把 handlechange(file, filelist)方法中的 file
、以及 filelist
打印出来看看。 file
指的是当前正在上传的 单个 img,filelist
是已上传的全部 img 列表。 当我上传完 两张图片后, 打印结果如下:
file的打印的结果如下:
{ "uid": "rc-upload-1551084269812-5", "width": 600, "height": 354, "lastmodified": 1546701318000, "lastmodifieddate": "2019-01-05t15:15:18.000z", "name": "e30e7b9680634b2c888c8bb513cc595d.jpg", "size": 31731, "type": "image/jpeg", "percent": 100, "originfileobj": { "uid": "rc-upload-1551084269812-5", "width": 600, "height": 354 }, "status": "done", "thumburl": "data:image/jpeg;base64,/9j/4aaqskzjrgabaqahq9qkklbimcxrih9o2vh/ac2t+ddpj98v+9rrwsuhnhdk0ar9qb5r0pb6vpb/qh9akkirr0irnt/vudr+nffjcrqwxjik5pb+dlj938akk06mysdkkkkbh//z", "response": { "retcode": 0, "imgurl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg", "photoid": 271850 } }
filelist 的打印结果:
[ { "uid": "rc-upload-1551084269812-3", "width": 1000, "height": 667, "lastmodified": 1501414799000, "lastmodifieddate": "2017-07-30t11:39:59.000z", "name": "29381f30e924b89914e91b33.jpg", "size": 135204, "type": "image/jpeg", "percent": 100, "originfileobj": { "uid": "rc-upload-1551084269812-3", "width": 1000, "height": 667 }, "status": "done", "thumburl": "data:image/jpeg;base64,/e3ju1tlak1fzjonhqu3lslv7ho6zrk11mzj7lut0a4fzuragi9quvzqq4iuej7zpqtg4djdspfl2lg733f8c4q+yhq8zoyfgsqommfwo5hull0hjiypdsypvxrdc1xqvxelrb8fvl/onolml9vrdvvys3ngfve2ysasoh71jfqyrqv2mxlhocccvsiyendyzo9xxb9kyh//z", "response": { "retcode": 0, "msg": "success", "imgurl": "http://qianguyihao.com/hfwpjouiurewnmbhepr689.jpg", } }, { "uid": "rc-upload-1551084269812-5", "width": 600, "height": 354, "lastmodified": 1546701318000, "lastmodifieddate": "2019-01-05t15:15:18.000z", "name": "e30e7b9680634b2c888c8bb513cc595d.jpg", "size": 31731, "type": "image/jpeg", "percent": 100, "originfileobj": { "uid": "rc-upload-1551084269812-5", "width": 600, "height": 354 }, "status": "done", "thumburl": "data:image/jpeg;base64,/9j/4aaqskzjrgabaqahq9qkklbimcxrih9o2vh/ac2t+ddpj98v+9rrwsuhnhdk0ar9qb5r0pb6vpb/qh9akkirr0irnt/vudr+nffjcrqwxjik5pb+dlj938akk06mysdkkkkbh//z", "response": { "retcode": 0, "imgurl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg", "photoid": 271850 } } ]
上方的json数据中,需要做几点解释:
(1)response
字段里面的数据,就是请求接口后,后台返回给前端的数据,里面包含了图片的url链接。
(2)status
字段里存放的是图片上传的实时状态,包括上传中、上传完成、上传失败。
(3)thumburl
字段里面存放的是图片的base64编码。
这个base64编码非常非常长。当点击图片预览的时候,其实就是加载的 thumburl 这个字段里的资源,难怪浏览器会卡死。
解决办法:在 handlechange方法里,图片上传成功后,将 thumburl 字段里面的 base64 编码改为真实的图片url。代码实现如下:
handlechange = ({ file, filelist }) => { console.log(json.stringify(file)); // file 是当前正在上传的 单个 img console.log(json.stringify(filelist)); // filelist 是已上传的全部 img 列表 if (file && file.response && file.response.retcode == 0) { console.log('图片上传成功'); filelist.foreach(item => { // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。 // 图片上传成功后,filelist数组中的 thumburl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。 item.thumburl = item.response.imgurl; }); } this.setstate({ imglist: filelist, }); };
新需求:编辑现有页面
上面一段的代码中,我们是在新建的页面中,从零开始上传图片。
现在有个新的需求:如何编辑现有的页面呢?也就是说说,现有的页面中,是默认有几张图片的。当我编辑这个页面时,可以对现有的图片做增删,也能增加新的图片。
我看到upload 组件有提供 defaultfilelist
的属性。我试了下,这个defaultfilelist
的属性根本没法儿用。
那就只要手动实现了。我的model层代码,是用 redux 写的。实现思路如下:
(1)pictureswall.js:
/* eslint-disable */ import { upload, icon, modal, form } from 'antd'; const formitem = form.item; class pictureswall extends purecomponent { state = { previewvisible: false, previewimage: '', }; // 页面初始化的时候,从接口拉取默认的图片数据 componentdidmount() { const { dispatch } = this.props; dispatch({ type: 'mymodel/getallinfo', payload: { params: xxx }, }); } handlechange = ({ file, filelist }) => { const { dispatch } = this.props; if (file && file.response && file.response.retcode == 0) { console.log('图片上传成功'); filelist.foreach(item => { // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。 // 图片上传成功后,filelist数组中的 thumburl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。 item.thumburl = item.response.imgurl; }); } dispatch({ type: 'mymodel/setimglist', payload: filelist, }); }; handlecancel = () => this.setstate({ previewvisible: false }); handlepreview = file => { this.setstate({ previewimage: file.url || file.thumburl, previewvisible: true, }); }; // 参考链接:https://www.jianshu.com/p/f356f050b3c9 handlebeforeupload = file => { //限制图片 格式、size、分辨率 const isjpg = file.type === 'image/jpeg'; const isjpeg = file.type === 'image/jpeg'; const isgif = file.type === 'image/gif'; const ispng = file.type === 'image/png'; if (!(isjpg || isjpeg || isgif || ispng)) { modal.error({ title: '只能上传jpg 、jpeg 、gif、 png格式的图片~', }); return; } const islt2m = file.size / 1024 / 1024 < 2; if (!islt2m) { modal.error({ title: '超过2m限制 不允许上传~', }); return; } return (isjpg || isjpeg || isgif || ispng) && islt2m && this.checkimagewh(file); }; //返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传 checkimagewh(file) { let self = this; return new promise(function(resolve, reject) { let filereader = new filereader(); filereader.onload = e => { let src = e.target.result; const image = new image(); image.onload = function() { // 获取图片的宽高,并存放到file对象中 console.log('file width :' + this.width); console.log('file height :' + this.height); file.width = this.width; file.height = this.height; resolve(); }; image.onerror = reject; image.src = src; }; filereader.readasdataurl(file); }); } handlesubmit = e => { const { dispatch, form } = this.props; e.preventdefault(); form.validatefieldsandscroll((err, values) => { // values 是form表单里的参数 // 点击按钮后,将表单提交给后台 dispatch({ type: 'mymodel/submitformdata', payload: values, }); }); }; render() { const { previewvisible, previewimage } = this.state; // 从 state 中拿数据 const { mymodel: { imglist }, // 从props中拿到的图片数据 } = this.props; const uploadbutton = ( <div> <icon type="plus" /> <div classname="ant-upload-text">upload</div> </div> ); return ( <div classname="clearfix"> <form onsubmit={this.handlesubmit} hiderequiredmark style={{ margintop: 8 }}> <formitem label="图片上传" {...formitemlayout}> {getfielddecorator('myimg')( <upload action="//jsonplaceholder.typicode.com/posts/" // 这个是接口请求 data={file => ({ // data里存放的是接口的请求参数 param1: myparam1, param2: myparam2, photocotent: file, // file 是当前正在上传的图片 photowidth: file.height, // 通过 handlebeforeupload 获取 图片的宽高 photoheight: file.width, })} listtype="picture-card" filelist={imglist} // 改为从 props 里拿图片数据,而不是从 state onpreview={this.handlepreview} // 点击图片缩略图,进行预览 beforeupload={this.handlebeforeupload} // 上传之前,对图片的格式做校验,并获取图片的宽高 onchange={this.handlechange} // 每次上传图片时,都会触发这个方法 > {this.state.imglist.length >= 9 ? null : uploadbutton} </upload> )} </formitem> </form> <modal visible={previewvisible} footer={null} oncancel={this.handlecancel}> <img alt="example" style={{ width: '100%' }} src={previewimage} /> </modal> </div> ); } } export default pictureswall;
(2)mymodel.js:
/* eslint-disable */ import { routerredux } from 'dva/router'; import { message, modal } from 'antd'; import { getgoodsinfo, getallgoods, } from '../services/api'; import { trim, getcookie } from '../utils/utils'; export default { namespace: 'mymodel', state: { form: {}, list: [], listdetail: [], goodslist: [], goodslistdetail: [], pagination: { pagesize: 10, total: 0, current: 1, }, imglist: [], //图片 }, subscriptions: { setup({ dispatch, history }) { history.listen(location => { if (location.pathname !== '/xx/xxx') return; if (!location.state || !location.state.xxxid) return; dispatch({ type: 'fetch', payload: location.state, }); }); }, }, effects: { // 接口。获取所有工厂店的列表 (步骤02) *getallinfo({ payload }, { select, call, put }) { yield put({ type: 'form', payload, }); console.log('params:' + json.stringify(payload)); let params = {}; params = payload; const response = yield call(getgoodsinfo, params); console.log('smyhvae response:' + json.stringify(response)); if (response.error) return; yield put({ type: 'allinfo', payload: (response.data && response.data.map(item => ({ xx1: item.yy1, xx2: item.yy2, }))) || [], }); // response 里包含了接口返回给前端的默认图片数据 if (response && response.data && response.data[0] && response.data[0].my_jpg) { let tempimglist = response.data[0].my_jpg.split(','); let imglist = []; if (tempimglist.length > 0) { tempimglist.foreach(item => { imglist.push({ uid: item, name: 'xxx.png', status: 'done', thumburl: item, }); }); } // 通过 redux的方式 将 默认图片 传给 imglist console.log('smyhvae payload imglist:' + json.stringify(imglist)); yield put({ type: 'setimglist', payload: imglist, }); } }, *setimglist({ payload }, { call, put }) { console.log('model setimglist'); yield put({ type: 'getimglist', payload, }); }, }, reducers: { allinfo(state, action) { return { ...state, list: action.payload, }; }, getimglist(state, action) { return { ...state, imglist: action.payload, }; }, }, };
大功告成。
本文感谢 ld 同学的支持。
最后一段
有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:
上一篇: sql语句 汉字转拼音首字母