handleProgress(file, loaded, total, idx) { let percent = (loaded / total * 100).toFixed(2) + '%'; let _progress = this.state.progress; _progress[idx] = percent; this.setState({ progress: _progress }) // 反馈到DOM里显示 }
拖拽上传
拖拽文件对于HTML5来说其实非常简单,因为它自带的几个事件监听机制可以直接做这类处理。主要用到的是下面三个:
onDragOver={(e)=>this.handleDragHover(e)} onDragLeave={(e)=>this.handleDragHover(e)} onDrop={(e)=>this.handleDrop(e)}
取消拖拽时的浏览器行为:
handleDragHover(e) { e.stopPropagation() e.preventDefault() }
处理拖拽进来的文件:
handleDrop(e) { this.setState({progress:[]}) this.handleDragHover(e) // 获取文件列表对象 let files = e.target.files || e.dataTransfer.files let count = this.state.multiple ? files.length : 1 for (let i = 0; i < count; i++) { files[i].thumb = URL.createObjectURL(files[i]) } // 转换为真正的数组 files = Array.prototype.slice.call(files, 0) // 过滤非图片类型的文件 files = files.filter(function (file) { return /image/i.test(file.type) }) this.setState({files: this.state.files.concat(files)}) }
多图同时上传
支持多图上传我们需要在组件调用处设置属性:
multiple = { true } // 开启多图上传 size = { 50 } // 一次最大上传数量(虽没有上限,为保证服务端正常,建议50以下)
然后我们可以使用 Promise.all() 处理异步操作队列:
handleUpload() { let _promises = this.state.files.map((file, idx) => this.upload(file, idx)) Promise.all(_promises).then( (res) => { // 全部上传完成 this.handleComplete() }).catch( (err) => { console.log(err) }) }
好了,前端工作已经完成,接下来就是Node的工作了。
后端实现
为了方便,后端采用的是express框架来快速搭建Http服务和路由。具体项目见我的github node-image-upload。逻辑虽然简单,但还是有几个可圈可点的地方:
跨域调用
本项目后端采用的是express,我们可以通过 res.header() 设置 请求的 “允许源” 来允许跨域调用:
res.header('Access-Control-Allow-Origin', '*');
设置为 * 说明允许任何 访问源,不太安全。建议设置成 你需要的 二级域名,如 jafeney.com。
除了 “允许源” ,其他还有 “允许头” 、”允许域”、 “允许方法”、”文本类型” 等。常用的设置如下:
function allowCross(res) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); }
ES6下的Ajax请求
ES6风格下的Ajax请求和ES5不太一样,在正式的请求发出之前都会先发一个 类型为 OPTIONS的请求 作为试探,只有当该请求通过以后,正式的请求才能发向服务端。
所以服务端路由 我们还需要 处理这样一个 请求:
router.options('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next(); });
注意:该请求同样需要设置跨域。
处理上传
处理上传的图片引人了multiparty模块,用法很简单:
/*使用multiparty处理上传的图片*/ router.post('/upload', function(req, res, next) { // 生成multiparty对象,并配置上传目标路径 var form = new multiparty.Form({uploadDir: './public/file/'}); // 上传完成后处理 form.parse(req, function(err, fields, files) { var filesTmp = JSON.stringify(files, null, 2); var relPath = ''; if (err) { // 保存失败 console.log('Parse error: ' + err); } else { // 图片保存成功! console.log('Parse Files: ' + filesTmp); // 图片处理 processImg(files); } }); });
图片处理
Node处理图片需要引入 gm 模块,它需要用 npm 来安装:
npm install gm --save
BUG说明
注意:node的图形操作gm模块前使用必须 先安装 imagemagick 和 graphicsmagick,Linux (ubuntu)上使用apt-get 安装:
sudo apt-get install imagemagick sudo apt-get install graphicsmagick --with-webp // 支持webp格式的图片
MacOS上可以用 Homebrew 直接安装:
brew install imagemagick brew install graphicsmagick --with-webp // 支持webp格式的图片
预设尺寸
有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用Promise来实现: