本文的最新內容,將在GitHub上更新,歡迎 star,和我一塊兒入門和進階前端。javascript
本次作後臺管理系統,採用的是 AntD 框架。涉及到圖片的上傳,用的是AntD的 upload 組件。前端
前端作文件上傳這個功能,是頗有技術難度的。既然框架給咱們提供好了,那就直接用唄。結果用的時候,發現 upload 組件的不少bug。下面來列舉幾個。java
備註:本文寫於2019-03-02,使用的 antd 版本是 3.13.6。git
由於須要上傳多張圖片,因此採用的是照片牆的形式。上傳成功後的界面以下:github
(1)上傳中:json
(2)上傳成功:redux
(3)圖片預覽:api
按照官方提供的實例,特此整理出項目開發中的完整寫法,親測有效。代碼以下:數組
/* 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,預覽圖片。但實際的結果是,瀏覽器必定會卡死。promise
定位問題發現,緣由居然是:圖片上傳成功後, 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 同窗的支持。
有人說,前端開發,連賣菜的都會。可若是真的遇到技術難題,仍是得找個靠譜的前端同窗才行。這不,來看看前端碼農平常: