前端AntD框架的upload组件上传图片时遇到的一些坑
前言
本文的最新内容,将在GitHub上更新,欢迎 star,和我一起入门和进阶前端。
本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 upload 组件。
前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 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 列表
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
fileList.forEach(imgItem => {
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
imgItem.thumbUrl = imgItem.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;
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
fileList.forEach(imgItem => {
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
imgItem.thumbUrl = imgItem.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';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!(isJPG || isJPEG || isGIF || isPNG)) {
Modal.error({
title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
});
} else if (!isLt2M) {
Modal.error({
title: '超过2M限制,不允许上传~',
});
}
}
// 参考链接:https://github.com/ant-design/ant-design/issues/8779
return new Promise((resolve, reject) => {
if (!(isJPG || isJPEG || isGIF || isPNG)) {
reject(file);
} else {
resolve(file && 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();
const {
mymodel: { imgList }, // 从props中拿默认的图片数据
} = this.props;
form.validateFieldsAndScroll((err, values) => {
// values 是form表单里的参数
// 点击按钮后,将表单提交给后台
// start 问题描述:当编辑现有页面时,如果针对已经存在的默认图片不做修改,则不会触发 upload 的 onChange方法。此时提交表单,表单里的 myImg 字段是空的。
// 解决办法:如果发现存在默认图片,则追加到表单中
if (!values.myImg) {
values.myImg = { fileList: [] };
values.myImg.fileList = imgList;
}
// end
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,
};
},
},
};
上面的代码,可以规避 upload 组件的一些bug;而且可以在上传前,通过校验图片的尺寸、大小等,如果不满足条件,则弹出modal弹窗,阻止上传。
大功告成。本文感谢 ld 同学的支持。
其他问题
最后一段
有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:
前端AntD框架的upload组件上传图片时遇到的一些坑的更多相关文章
- AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为
本次做后台管理系统,采用的是 AntD 框架.涉及到图片的上传,用的是AntD的 upload 组件. 我在上一篇文章<AntD框架的upload组件上传图片时使用customRequest方法 ...
- iview中upload组件上传图片,跨域
前提:先前开发了一个A项目,A项目中有一套上传图片的接口,现在开发B项目. B项目开发中用iview中的upload组件上传图片,用到了A项目中上传接口,爬坑经历 1.涉及到了跨域解决:后台配置一下文 ...
- vue问题三:element ui的upload组件上传图片成功和移除事件
element ui的upload组件上传图片成功和移除事件: 登录后获取到后台传的token存到中: sessionStorage.setItem("token",data.ob ...
- React antd如何实现<Upload>组件上传附件再次上传已清除附件缓存问题。
最近在公司做React+antd的项目,遇到一个上传组件的问题,即上传附件成功后,文件展示处仍然还有之前上传附件的缓存信息,需要解决的问题是,要把上一次上传的附件缓存在上传成功或者取消后,可以进行清除 ...
- Element upload组件上传图片与回显图片
场景:新增商品时需要添加商品主图,新增成功之后可编辑 上传图片: <el-form-item label="专区logo:" style="height:160px ...
- 从零开始,搭建博客系统MVC5+EF6搭建框架(4)上,前后台页面布局页面实现,介绍使用的UI框架以及JS组件
一.博客系统进度回顾以及页面设计 1.1页面设计说明 紧接前面基础基本完成了框架搭建,现在开始设计页面,前台页面设计我是模仿我博客园的风格来设计的,后台是常规的左右布局风格. 1.2前台页面风格 主页 ...
- 【干货】利用MVC5+EF6搭建博客系统(四)(上)前后台页面布局页面实现,介绍使用的UI框架以及JS组件
一.博客系统进度回顾以及页面设计 1.1页面设计说明 紧接前面基础基本完成了框架搭建,现在开始设计页面,前台页面设计我是模仿我博客园的风格来设计的,后台是常规的左右布局风格. 1.2前台页面风格 主页 ...
- 前端mv框架下(目前写的是vue),对组件抽象的思考
前沿: 抽象是门大学问.前端mv框架中,以组件化的概念为主.经常会考虑抽象到组件级别,进行复用.合理的抽象,能提高效率,减少业务逻辑视图的耦合程度.不合理的抽象,则会增加代码的复杂程度. 遇到的问题 ...
- 目前比较火的前端框架及UI组件
看到的一篇总结性的文章,收藏一下,感兴趣的可以自己看看,哪些是已经会的,哪些是没听说过的,哪些是一知半解的,都可以稍微看看. 一.前端框架库: 1.Zepto.js 地址:点击打开链接 描述:Zept ...
随机推荐
- 解决关于ios访问相机闪退问题
在mac上的ionic3项目打包成苹果app,系统版本是10.3.3 . 当调用相机的时候出现闪退情况,这是调试出现的问题: This app has crashed because it attem ...
- Pulsar-Producer实现简介
“Pulsar is a distributed pub-sub messaging platform with a very flexible messaging model and an intu ...
- 【杂谈】tocmat是何时写回响应数据报的
疑问的产生 这个疑问是我在写文件下载的时候产生的,我是用HttpServletResponse获取到Outputstream,然后利用OutputStream直接写数据的.当时我就想这个OutputS ...
- SharePoint如何配置Ipad跳转等问题
如何配置Ipad跳转 Apple iPad 设备上不支持 SharePoint 标准视图.用户可以改用移动视图在 iPad 设备上查看 SharePoint 内容.默认情况下,iPad 用户被重定向到 ...
- [转]Memcache的使用和协议分析详解
Memcache是什么 Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力. 它可以应 ...
- FineUI开源版(ASP.Net)初学手册-部分JS整理
有人老找JS,我吧FineUI自己写的JS沾过来方便大家看看,在实现前端的时候更灵活 JS 实例 注释 控件 F.ready F.ready(function(){}); 就是ready 很多方法 ...
- Mysql加锁过程详解(7)-初步理解MySQL的gap锁
Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...
- Perl使用模块和@INC
use加载模块 安装模块后,都会有对应的文档,可以通过perldoc MODULE_NAME来获取模块的使用帮助. 例如:获取File::Utils的使用帮助. perldoc File::Utils ...
- Python作用域详述
作用域是指变量的生效范围,例如本地变量.全局变量描述的就是不同的生效范围. python的变量作用域的规则非常简单,可以说是所有语言中最直观.最容易理解的作用域. 在开始介绍作用域之前,先抛一个问题: ...
- Go基础系列:WaitGroup用法说明
正常情况下,新激活的goroutine的结束过程是不可控制的,唯一可以保证终止goroutine的行为是main goroutine的终止.也就是说,我们并不知道哪个goroutine什么时候结束. ...