diff --git a/13-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md b/13-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md
new file mode 100644
index 0000000..1b74e34
--- /dev/null
+++ b/13-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md
@@ -0,0 +1,590 @@
+
+## 前言
+
+本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。
+
+前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 upload 组件的很多bug。
+
+## 使用 AntD 的 upload 组件做图片的上传
+
+因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:
+
+按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:
+
+```javascript
+/* 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 = (
+
+ );
+ return (
+
+
+
+
+
+
+ );
+ }
+}
+
+export default PicturesWall;
+
+```
+
+
+## 上传后,点击图片预览,浏览器卡死的问题
+
+依据上方的代码,通过 Antd 的 upload 组件将图片上传成功后,点击图片的缩略图,理应可以在当前页面弹出 Modal,预览图片。但实际的结果是,浏览器一定会卡死。
+
+定位问题发现,原因竟然是:图片上传成功后, upload 会将其转为 base64编码。base64这个字符串太大了,点击图片预览的时候,浏览器在解析一大串字符串,然后就卡死了。详细过程描述如下。
+
+上方代码中,我们可以把 handleChange(file, fileList)方法中的 `file`、以及 `fileList`打印出来看看。 `file`指的是当前正在上传的 单个 img,`fileList`是已上传的全部 img 列表。 当我上传完 两张图片后, 打印结果如下:
+
+
+file的打印的结果如下:
+
+```json
+ {
+ "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 的打印结果:
+
+```json
+[
+ {
+ "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。代码实现如下:
+
+```javascript
+ 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,
+ });
+ };
+```
+
+
+## 新需求:编辑现有页面
+
+上面一段的代码中,我们是在新建的页面中,从零开始上传页面。
+
+现在有个新的需求:如何编辑现有的页面呢?也就是说说,现有的页面中,是默认有几张图片的。当我编辑这个页面时,可以对现有的图片做增删,也能增加新的图片。
+
+我的model层代码,是用 redux 写的。实现思路如下:
+
+
+(1)PicturesWall.js:
+
+```javascript
+/* 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 = (
+
+ );
+ return (
+
+
+
+
+
+
+ );
+ }
+}
+
+export default PicturesWall;
+
+```
+
+(2)mymodel.js:
+
+```javascript
+/* eslint-disable */
+
+import { routerRedux } from 'dva/router';
+import { message, Modal } from 'antd';
+import {
+ getTuanList,
+ getAllFactory,
+ getAllFactGoods,
+ createFactShop,
+ updateFactShop,
+ deleteFactShop,
+ updateFactGoodsStatus,
+ queryShopDetail,
+ createFactGoods,
+} from '../services/api';
+import { trim, getCookie } from '../utils/utils';
+
+export default {
+ namespace: 'factory',
+
+ 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(getAllFactory, 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('smyhvae 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 同学的支持。
+
+
+
+
+
+
+
+
+
+
+
+