经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » JavaScript » 查看文章
AntD框架的upload组件上传图片时遇到的一些坑
来源:cnblogs  作者:千古壹号  时间:2019/3/4 9:16:40  对本文有异议

前言

本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 upload 组件。

前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 upload 组件的很多bug。下面来列举几个。

备注:本文写于2019-03-02,使用的 antd 版本是 3.13.6。

使用 AntD 的 upload 组件做图片的上传

因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:

(1)上传中:

(2)上传成功:

(3)图片预览:

按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:

  1. /* eslint-disable */
  2. import { Upload, Icon, Modal, Form } from 'antd';
  3. const FormItem = Form.Item;
  4. class PicturesWall extends PureComponent {
  5. state = {
  6. previewVisible: false,
  7. previewImage: '',
  8. imgList: [],
  9. };
  10. handleChange = ({ file, fileList }) => {
  11. console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
  12. console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
  13. this.setState({
  14. imgList: fileList,
  15. });
  16. };
  17. handleCancel = () => this.setState({ previewVisible: false });
  18. handlePreview = file => {
  19. this.setState({
  20. previewImage: file.url || file.thumbUrl,
  21. previewVisible: true,
  22. });
  23. };
  24. // 参考链接:https://www.jianshu.com/p/f356f050b3c9
  25. handleBeforeUpload = file => {
  26. //限制图片 格式、size、分辨率
  27. const isJPG = file.type === 'image/jpeg';
  28. const isJPEG = file.type === 'image/jpeg';
  29. const isGIF = file.type === 'image/gif';
  30. const isPNG = file.type === 'image/png';
  31. if (!(isJPG || isJPEG || isGIF || isPNG)) {
  32. Modal.error({
  33. title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
  34. });
  35. return;
  36. }
  37. const isLt2M = file.size / 1024 / 1024 < 2;
  38. if (!isLt2M) {
  39. Modal.error({
  40. title: '超过2M限制 不允许上传~',
  41. });
  42. return;
  43. }
  44. return (isJPG || isJPEG || isGIF || isPNG) && isLt2M && this.checkImageWH(file);
  45. };
  46. //返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
  47. checkImageWH(file) {
  48. let self = this;
  49. return new Promise(function(resolve, reject) {
  50. let filereader = new FileReader();
  51. filereader.onload = e => {
  52. let src = e.target.result;
  53. const image = new Image();
  54. image.onload = function() {
  55. // 获取图片的宽高,并存放到file对象中
  56. console.log('file width :' + this.width);
  57. console.log('file height :' + this.height);
  58. file.width = this.width;
  59. file.height = this.height;
  60. resolve();
  61. };
  62. image.onerror = reject;
  63. image.src = src;
  64. };
  65. filereader.readAsDataURL(file);
  66. });
  67. }
  68. handleSubmit = e => {
  69. const { dispatch, form } = this.props;
  70. e.preventDefault();
  71. form.validateFieldsAndScroll((err, values) => {// values 是form表单里的参数
  72. // 点击按钮后,将表单提交给后台
  73. dispatch({
  74. type: 'mymodel/submitFormData',
  75. payload: values,
  76. });
  77. });
  78. };
  79. render() {
  80. const { previewVisible, previewImage, imgList } = this.state; // 从 state 中拿数据
  81. const uploadButton = (
  82. <div>
  83. <Icon type="plus" />
  84. <div className="ant-upload-text">Upload</div>
  85. </div>
  86. );
  87. return (
  88. <div className="clearfix">
  89. <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
  90. <FormItem label="图片图片" {...formItemLayout}>
  91. {getFieldDecorator('myImg')(
  92. <Upload
  93. action="//jsonplaceholder.typicode.com/posts/" // 这个是接口请求
  94. data={file => ({ // data里存放的是接口的请求参数
  95. param1: myParam1,
  96. param2: myParam2,
  97. photoCotent: file, // file 是当前正在上传的图片
  98. photoWidth: file.height, // 通过 handleBeforeUpload 获取 图片的宽高
  99. photoHeight: file.width,
  100. })}
  101. listType="picture-card"
  102. fileList={this.state.imgList}
  103. onPreview={this.handlePreview} // 点击图片缩略图,进行预览
  104. beforeUpload={this.handleBeforeUpload} // 上传之前,对图片的格式做校验,并获取图片的宽高
  105. onChange={this.handleChange} // 每次上传图片时,都会触发这个方法
  106. >
  107. {this.state.imgList.length >= 9 ? null : uploadButton}
  108. </Upload>
  109. )}
  110. </FormItem>
  111. </Form>
  112. <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
  113. <img alt="example" style={{ width: '100%' }} src={previewImage} />
  114. </Modal>
  115. </div>
  116. );
  117. }
  118. }
  119. export default PicturesWall;

上传后,点击图片预览,浏览器卡死的问题

依据上方的代码,通过 Antd 的 upload 组件将图片上传成功后,点击图片的缩略图,理应可以在当前页面弹出 Modal,预览图片。但实际的结果是,浏览器一定会卡死。

定位问题发现,原因竟然是:图片上传成功后, upload 会将其转为 base64编码。base64这个字符串太大了,点击图片预览的时候,浏览器在解析一大串字符串,然后就卡死了。详细过程描述如下。

上方代码中,我们可以把 handleChange(file, fileList)方法中的 file、以及 fileList打印出来看看。 file指的是当前正在上传的 单个 img,fileList是已上传的全部 img 列表。 当我上传完 两张图片后, 打印结果如下:

file的打印的结果如下:

  1. {
  2. "uid": "rc-upload-1551084269812-5",
  3. "width": 600,
  4. "height": 354,
  5. "lastModified": 1546701318000,
  6. "lastModifiedDate": "2019-01-05T15:15:18.000Z",
  7. "name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
  8. "size": 31731,
  9. "type": "image/jpeg",
  10. "percent": 100,
  11. "originFileObj": {
  12. "uid": "rc-upload-1551084269812-5",
  13. "width": 600,
  14. "height": 354
  15. },
  16. "status": "done",
  17. "thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
  18. "response": {
  19. "retCode": 0,
  20. "imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
  21. "photoid": 271850
  22. }
  23. }

fileList 的打印结果:

  1. [
  2. {
  3. "uid": "rc-upload-1551084269812-3",
  4. "width": 1000,
  5. "height": 667,
  6. "lastModified": 1501414799000,
  7. "lastModifiedDate": "2017-07-30T11:39:59.000Z",
  8. "name": "29381f30e924b89914e91b33.jpg",
  9. "size": 135204,
  10. "type": "image/jpeg",
  11. "percent": 100,
  12. "originFileObj": {
  13. "uid": "rc-upload-1551084269812-3",
  14. "width": 1000,
  15. "height": 667
  16. },
  17. "status": "done",
  18. "thumbUrl": "data:image/jpeg;base64,/E3ju1tlaK1fzJOnHQU3LsLV7HO6Zrk11MZJ7luT0A4FZuRagi9quvzQQ4iuEJ7ZpqTG4djDsPFl2Lg733f8C4q+YhQ8zoYfGSqoMmfwo5huLL0HjiyPDSYPvxRdC1XQvxeLrB8fvl/OnoLmL9vrdvvYS3NGFVe2YsASOh71JfQyrqV2mXLHOcccVSIYEnDyZO9XXB9KYH//Z",
  19. "response": {
  20. "retCode": 0,
  21. "msg": "success",
  22. "imgUrl": "http://qianguyihao.com/hfwpjouiurewnmbhepr689.jpg",
  23. }
  24. },
  25. {
  26. "uid": "rc-upload-1551084269812-5",
  27. "width": 600,
  28. "height": 354,
  29. "lastModified": 1546701318000,
  30. "lastModifiedDate": "2019-01-05T15:15:18.000Z",
  31. "name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
  32. "size": 31731,
  33. "type": "image/jpeg",
  34. "percent": 100,
  35. "originFileObj": {
  36. "uid": "rc-upload-1551084269812-5",
  37. "width": 600,
  38. "height": 354
  39. },
  40. "status": "done",
  41. "thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
  42. "response": {
  43. "retCode": 0,
  44. "imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
  45. "photoid": 271850
  46. }
  47. }
  48. ]

上方的json数据中,需要做几点解释:

(1)response 字段里面的数据,就是请求接口后,后台返回给前端的数据,里面包含了图片的url链接。

(2)status 字段里存放的是图片上传的实时状态,包括上传中、上传完成、上传失败。

(3)thumbUrl字段里面存放的是图片的base64编码。

这个base64编码非常非常长。当点击图片预览的时候,其实就是加载的 thumbUrl 这个字段里的资源,难怪浏览器会卡死。

解决办法:在 handleChange方法里,图片上传成功后,将 thumbUrl 字段里面的 base64 编码改为真实的图片url。代码实现如下:

  1. handleChange = ({ file, fileList }) => {
  2. console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
  3. console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
  4. if (file && file.response && file.response.retCode == 0) {
  5. console.log('图片上传成功');
  6. fileList.forEach(item => {
  7. // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
  8. // 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
  9. item.thumbUrl = item.response.imgUrl;
  10. });
  11. }
  12. this.setState({
  13. imgList: fileList,
  14. });
  15. };

新需求:编辑现有页面

上面一段的代码中,我们是在新建的页面中,从零开始上传图片。

现在有个新的需求:如何编辑现有的页面呢?也就是说说,现有的页面中,是默认有几张图片的。当我编辑这个页面时,可以对现有的图片做增删,也能增加新的图片。

我看到upload 组件有提供 defaultFileList 的属性。我试了下,这个defaultFileList 的属性根本没法儿用。

那就只要手动实现了。我的model层代码,是用 redux 写的。实现思路如下:

(1)PicturesWall.js:

  1. /* eslint-disable */
  2. import { Upload, Icon, Modal, Form } from 'antd';
  3. const FormItem = Form.Item;
  4. class PicturesWall extends PureComponent {
  5. state = {
  6. previewVisible: false,
  7. previewImage: '',
  8. };
  9. // 页面初始化的时候,从接口拉取默认的图片数据
  10. componentDidMount() {
  11. const { dispatch } = this.props;
  12. dispatch({
  13. type: 'mymodel/getAllInfo',
  14. payload: { params: xxx },
  15. });
  16. }
  17. handleChange = ({ file, fileList }) => {
  18. const { dispatch } = this.props;
  19. if (file && file.response && file.response.retCode == 0) {
  20. console.log('图片上传成功');
  21. fileList.forEach(item => {
  22. // 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
  23. // 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
  24. item.thumbUrl = item.response.imgUrl;
  25. });
  26. }
  27. dispatch({
  28. type: 'mymodel/setImgList',
  29. payload: fileList,
  30. });
  31. };
  32. handleCancel = () => this.setState({ previewVisible: false });
  33. handlePreview = file => {
  34. this.setState({
  35. previewImage: file.url || file.thumbUrl,
  36. previewVisible: true,
  37. });
  38. };
  39. // 参考链接:https://www.jianshu.com/p/f356f050b3c9
  40. handleBeforeUpload = file => {
  41. //限制图片 格式、size、分辨率
  42. const isJPG = file.type === 'image/jpeg';
  43. const isJPEG = file.type === 'image/jpeg';
  44. const isGIF = file.type === 'image/gif';
  45. const isPNG = file.type === 'image/png';
  46. if (!(isJPG || isJPEG || isGIF || isPNG)) {
  47. Modal.error({
  48. title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
  49. });
  50. return;
  51. }
  52. const isLt2M = file.size / 1024 / 1024 < 2;
  53. if (!isLt2M) {
  54. Modal.error({
  55. title: '超过2M限制 不允许上传~',
  56. });
  57. return;
  58. }
  59. return (isJPG || isJPEG || isGIF || isPNG) && isLt2M && this.checkImageWH(file);
  60. };
  61. //返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
  62. checkImageWH(file) {
  63. let self = this;
  64. return new Promise(function(resolve, reject) {
  65. let filereader = new FileReader();
  66. filereader.onload = e => {
  67. let src = e.target.result;
  68. const image = new Image();
  69. image.onload = function() {
  70. // 获取图片的宽高,并存放到file对象中
  71. console.log('file width :' + this.width);
  72. console.log('file height :' + this.height);
  73. file.width = this.width;
  74. file.height = this.height;
  75. resolve();
  76. };
  77. image.onerror = reject;
  78. image.src = src;
  79. };
  80. filereader.readAsDataURL(file);
  81. });
  82. }
  83. handleSubmit = e => {
  84. const { dispatch, form } = this.props;
  85. e.preventDefault();
  86. form.validateFieldsAndScroll((err, values) => {
  87. // values 是form表单里的参数
  88. // 点击按钮后,将表单提交给后台
  89. dispatch({
  90. type: 'mymodel/submitFormData',
  91. payload: values,
  92. });
  93. });
  94. };
  95. render() {
  96. const { previewVisible, previewImage } = this.state; // 从 state 中拿数据
  97. const {
  98. mymodel: { imgList }, // 从props中拿到的图片数据
  99. } = this.props;
  100. const uploadButton = (
  101. <div>
  102. <Icon type="plus" />
  103. <div className="ant-upload-text">Upload</div>
  104. </div>
  105. );
  106. return (
  107. <div className="clearfix">
  108. <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
  109. <FormItem label="图片上传" {...formItemLayout}>
  110. {getFieldDecorator('myImg')(
  111. <Upload
  112. action="//jsonplaceholder.typicode.com/posts/" // 这个是接口请求
  113. data={file => ({
  114. // data里存放的是接口的请求参数
  115. param1: myParam1,
  116. param2: myParam2,
  117. photoCotent: file, // file 是当前正在上传的图片
  118. photoWidth: file.height, // 通过 handleBeforeUpload 获取 图片的宽高
  119. photoHeight: file.width,
  120. })}
  121. listType="picture-card"
  122. fileList={imgList} // 改为从 props 里拿图片数据,而不是从 state
  123. onPreview={this.handlePreview} // 点击图片缩略图,进行预览
  124. beforeUpload={this.handleBeforeUpload} // 上传之前,对图片的格式做校验,并获取图片的宽高
  125. onChange={this.handleChange} // 每次上传图片时,都会触发这个方法
  126. >
  127. {this.state.imgList.length >= 9 ? null : uploadButton}
  128. </Upload>
  129. )}
  130. </FormItem>
  131. </Form>
  132. <Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
  133. <img alt="example" style={{ width: '100%' }} src={previewImage} />
  134. </Modal>
  135. </div>
  136. );
  137. }
  138. }
  139. export default PicturesWall;

(2)mymodel.js:

  1. /* eslint-disable */
  2. import { routerRedux } from 'dva/router';
  3. import { message, Modal } from 'antd';
  4. import {
  5. getGoodsInfo,
  6. getAllGoods,
  7. } from '../services/api';
  8. import { trim, getCookie } from '../utils/utils';
  9. export default {
  10. namespace: 'mymodel',
  11. state: {
  12. form: {},
  13. list: [],
  14. listDetail: [],
  15. goodsList: [],
  16. goodsListDetail: [],
  17. pagination: {
  18. pageSize: 10,
  19. total: 0,
  20. current: 1,
  21. },
  22. imgList: [], //图片
  23. },
  24. subscriptions: {
  25. setup({ dispatch, history }) {
  26. history.listen(location => {
  27. if (location.pathname !== '/xx/xxx') return;
  28. if (!location.state || !location.state.xxxId) return;
  29. dispatch({
  30. type: 'fetch',
  31. payload: location.state,
  32. });
  33. });
  34. },
  35. },
  36. effects: {
  37. // 接口。获取所有工厂店的列表 (步骤02)
  38. *getAllInfo({ payload }, { select, call, put }) {
  39. yield put({
  40. type: 'form',
  41. payload,
  42. });
  43. console.log('params:' + JSON.stringify(payload));
  44. let params = {};
  45. params = payload;
  46. const response = yield call(getGoodsInfo, params);
  47. console.log('smyhvae response:' + JSON.stringify(response));
  48. if (response.error) return;
  49. yield put({
  50. type: 'allInfo',
  51. payload:
  52. (response.data &&
  53. response.data.map(item => ({
  54. xx1: item.yy1,
  55. xx2: item.yy2,
  56. }))) ||
  57. [],
  58. });
  59. // response 里包含了接口返回给前端的默认图片数据
  60. if (response && response.data && response.data[0] && response.data[0].my_jpg) {
  61. let tempImgList = response.data[0].my_jpg.split(',');
  62. let imgList = [];
  63. if (tempImgList.length > 0) {
  64. tempImgList.forEach(item => {
  65. imgList.push({
  66. uid: item,
  67. name: 'xxx.png',
  68. status: 'done',
  69. thumbUrl: item,
  70. });
  71. });
  72. }
  73. // 通过 redux的方式 将 默认图片 传给 imgList
  74. console.log('smyhvae payload imgList:' + JSON.stringify(imgList));
  75. yield put({
  76. type: 'setImgList',
  77. payload: imgList,
  78. });
  79. }
  80. },
  81. *setImgList({ payload }, { call, put }) {
  82. console.log('model setImgList');
  83. yield put({
  84. type: 'getImgList',
  85. payload,
  86. });
  87. },
  88. },
  89. reducers: {
  90. allInfo(state, action) {
  91. return {
  92. ...state,
  93. list: action.payload,
  94. };
  95. },
  96. getImgList(state, action) {
  97. return {
  98. ...state,
  99. imgList: action.payload,
  100. };
  101. },
  102. },
  103. };

大功告成。

本文感谢 ld 同学的支持。

最后一段

有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:

原文链接:http://www.cnblogs.com/qianguyihao/p/10460834.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号