Laravel 5.1 中基于 Dropzone.js 实现图片拖拽上传及删除功能
1、概述
Dropzone 是目前最好的免费文件拖拽上传库,它拥有很多特性和选项以便你可以使用多种方式来自定义。
在Laravel项目集成Dropzone对没有经验的新手来说有点棘手,所以接下来我想你给你们展示最优雅的解决方案。
这篇教程将包括以下内容:
- 自动 图片 上传
- 通过Ajax请求从Dropzone预览中直接移除图片
- 上传图片计数器
- 保存完整尺寸图片和图标尺寸版本
- 使用Intervention Image包调整图片尺寸和编码
- 保存图片数据到数据库
- 在服务器端生成唯一的图片名称
如果你还需要上传或裁剪图片,请参考上一篇教程: 在 Laravel 5 中使用 jQuery 插件 Croppic + Intervention Image 实现图片上传和裁剪 。
本教程的完整代码已公开在GitHub上: https://github.com/codingo-me/dropzone-laravel-image-upload
最终演示效果图如下:
2、项目基本配置
这里我们安装一个新的Laravel应用并创建包含数据库凭证信息的.env文件。
此外我们还要从 https://github.com/enyo/dropzone/tree/master/dist 下载样式和脚本文件,这些文件存放到 public/packages/dropzone 。
前端我们还是使用Bootstrap,相关文件存放在 public/packages/bootstrap 。
上面已经提到我们要使用Intervention Image扩展包来处理上传图片,并且这里我们会在Blade模板中用到html辅助函数,所以我们需要安装相关依赖,安装Intervention Image请参考: 在 Laravel 5 中集成 Intervention Image 实现对图片的创建、修改和压缩处理 ,而安装html请参考: Laravel 5 中使用 HtmlBuilder 及 URL::asset() 引入站内或站外的 css 和 js 文件 。
3、视图
本教程只包含单个页面用于上传图片,尽管这样我还是喜欢将布局和具体实现页面分开,我们编辑布局文件 layout.blade.php 如下:
Image upload in Laravel 5.1 with Dropzone.js {!! HTML::style('/packages/bootstrap/css/bootstrap.min.css') !!} {!! HTML::style('/assets/css/style.css') !!} {!! HTML::script('https://code.jquery.com/jquery-2.1.4.min.js') !!} @yield('head')@yield('footer')
@yield('content')
接下来我们创建上传图片的视图文件 pages/upload.blade.php :
@extends('layout')@section('head') {!! HTML::style('/packages/dropzone/dropzone.css') !!}@stop@section('footer') {!! HTML::script('/packages/dropzone/dropzone.js') !!} {!! HTML::script('/assets/js/dropzone-config.js') !!}@stop@section('content')@stopImages
{!! Form::open(['url' => route('upload-post'), 'class' => 'dropzone', 'files'=>true, 'id'=>'real-dropzone']) !!}Drop images in this area
{!! Form::close() !!}
- Images are uploaded as soon as you drop them
- Maximum allowed size of image is 8MB
在上传视图的 head 部分我们引入了Dropzone的默认样式,在 footer 部分我们传递默认的JavaScript文件以及我们自己的Dropzone配置文件。
内容部分包含两个部分,首先是上传表单然后是隐藏的图片预览区域,我是从Dropzone官网下载的预览模板代码,在配置文件中使用这个模板告诉Dropzone哪段HTML用于上传图片预览。
4、Dropzone配置
查看完整的Dropzone配置项列表参考 这里 ,对本项目来说我想从Dropzone一个一个上传文件,因为这样的话我就不需要循环遍历文件上传,并行上传文件数限制为100并且每个文件大小不能超过8M。
每个图片预览都对应一个删除链接,点击该链接Dropzone就会触发 removedfile 事件,该事件会创建上传/删除的Ajax请求,如果响应状态码是200,则对应图片计数器减少。
var photo_counter = 0;Dropzone.options.realDropzone = { uploadMultiple: false, parallelUploads: 100, maxFilesize: 8, previewsContainer: '#dropzonePreview', previewTemplate: document.querySelector('#preview-template').innerHTML, addRemoveLinks: true, dictRemoveFile: 'Remove', dictFileTooBig: 'Image is bigger than 8MB', // The setting up of the dropzone init:function() { this.on("removedfile", function(file) { $.ajax({ type: 'POST', url: 'upload/delete', data: {id: file.name}, dataType: 'html', success: function(data){ var rep = JSON.parse(data); if(rep.code == 200) { photo_counter--; $("#photoCounter").text( "(" + photo_counter + ")"); } } }); } ); }, error: function(file, response) { if($.type(response) === "string") var message = response; //dropzone sends it's own error messages in string else var message = response.message; file.previewElement.classList.add("dz-error"); _ref = file.previewElement.querySelectorAll("[data-dz-errormessage]"); _results = []; for (_i = 0, _len = _ref.length; _i该配置文件底部如果上传成功图片计数器增加。
5、上传逻辑
我习惯将特定逻辑放到Repository类,在本项目中我使用 ImageRepository 这个类,该类存放在 app/Logic/Image/ImageRepository.php 。
目前这个类只做两件事,上传单个文件以及删除指定文件,我们将整个Repository类注入到 ImageController 中:
image = $imageRepository; } public function getUpload() { return view('pages.upload'); } public function postUpload() { $photo = Input::all(); $response = $this->image->upload($photo); return $response; } public function deleteUpload() { $filename = Input::get('id'); if(!$filename) { return 0; } $response = $this->image->delete( $filename ); return $response; }}因此对这个控制器而言我需要三个路由:显示上传表单,上传以及删除请求:
Route::get('/', ['as' => 'upload', 'uses' => 'ImageController@getUpload']);Route::post('upload', ['as' => 'upload-post', 'uses' =>'ImageController@postUpload']);Route::post('upload/delete', ['as' => 'upload-remove', 'uses' =>'ImageController@deleteUpload']);ImageController 的 postUpload 方法将用户上传请求转发到 ImageRepository 类对应的 upload 方法。这个 upload 方法首先会验证输入,然后为上传文件分配唯一的文件名,再然后保存原始尺寸图片到指定目录,同时将图标尺寸图片保存到其它目录,此外还要保存到数据库以便Laravel可以跟踪图片上传记录。为此我们需要创建迁移文件并运行迁移命令:
increments('id'); $table->text('original_name'); $table->text('filename'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('images'); }}我已经提过了上传文件将会保存为两种尺寸,此外可能还要生成缩略图并对图片编码,要实现这些功能,我们需要使用Intervention Image扩展包:
fails()) { return Response::json([ 'error' => true, 'message' => $validator->messages()->first(), 'code' => 400 ], 400); } $photo = $form_data['file']; $originalName = $photo->getClientOriginalName(); $originalNameWithoutExt = substr($originalName, 0, strlen($originalName) - 4); $filename = $this->sanitize($originalNameWithoutExt); $allowed_filename = $this->createUniqueFilename( $filename ); $filenameExt = $allowed_filename .'.jpg'; $uploadSuccess1 = $this->original( $photo, $filenameExt ); $uploadSuccess2 = $this->icon( $photo, $filenameExt ); if( !$uploadSuccess1 || !$uploadSuccess2 ) { return Response::json([ 'error' => true, 'message' => 'Server error while uploading', 'code' => 500 ], 500); } $sessionImage = new Image; $sessionImage->filename = $allowed_filename; $sessionImage->original_name = $originalName; $sessionImage->save(); return Response::json([ 'error' => false, 'code' => 200 ], 200); } public function createUniqueFilename( $filename ) { $full_size_dir = Config::get('images.full_size'); $full_image_path = $full_size_dir . $filename . '.jpg'; if ( File::exists( $full_image_path ) ) { // Generate token for image $imageToken = substr(sha1(mt_rand()), 0, 5); return $filename . '-' . $imageToken; } return $filename; } /** * Optimize Original Image */ public function original( $photo, $filename ) { $manager = new ImageManager(); $image = $manager->make( $photo )->encode('jpg')->save(Config::get('images.full_size') . $filename ); return $image; } /** * Create Icon From Original */ public function icon( $photo, $filename ) { $manager = new ImageManager(); $image = $manager->make( $photo )->encode('jpg')->resize(200, null, function($constraint){$constraint->aspectRatio();})->save( Config::get('images.icon_size') . $filename ); return $image; } /** * Delete Image From Session folder, based on original filename */ public function delete( $originalFilename) { $full_size_dir = Config::get('images.full_size'); $icon_size_dir = Config::get('images.icon_size'); $sessionImage = Image::where('original_name', 'like', $originalFilename)->first(); if(empty($sessionImage)) { return Response::json([ 'error' => true, 'code' => 400 ], 400); } $full_path1 = $full_size_dir . $sessionImage->filename . '.jpg'; $full_path2 = $icon_size_dir . $sessionImage->filename . '.jpg'; if ( File::exists( $full_path1 ) ) { File::delete( $full_path1 ); } if ( File::exists( $full_path2 ) ) { File::delete( $full_path2 ); } if( !empty($sessionImage)) { $sessionImage->delete(); } return Response::json([ 'error' => false, 'code' => 200 ], 200); } function sanitize($string, $force_lowercase = true, $anal = false) { $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]", "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—", "—", "–", ",", "", "/", "?"); $clean = trim(str_replace($strip, "", strip_tags($string))); $clean = preg_replace('/\s+/', "-", $clean); $clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ; return ($force_lowercase) ? (function_exists('mb_strtolower')) ? mb_strtolower($clean, 'UTF-8') : strtolower($clean) : $clean; }}上传方法会验证 Image 模型中指定的字段规则,目前该规则只是限定上传图片的扩展。
在 upload 方法之后我们使用 reateUniqueFilename 方法为图片返回唯一文件名。
当唯一文件名返回后,我们将图片和对应文件名传递到 original 方法,该方法负责保存完整尺寸图片,对小尺寸图标图片我们使用 icon 方法进行处理。
这两个尺寸图片都保存好了之后,系统会在图片表 images 中创建一条新纪录。
你可能已经注意到了我将图片上传目录存放在了配置文件中,当然你可以自定义存放目录或者直接使用Laravel内置的方法。
env('UPLOAD_FULL_SIZE'), 'icon_size' => env('UPLOAD_ICON_SIZE'),];要让应用正常工作还需要将 .env.example 文件拷贝到 .env 文件,并且设置相应的配置值。注意到最后两个配置项的上传目录需要以 / 结尾,以我本地配置为例:
UPLOAD_FULL_SIZE=F:/xampp/htdocs/nonfu/dropzone-laravel-image-upload/public/images/full_size/UPLOAD_ICON_SIZE=F:/xampp/htdocs/nonfu/dropzone-laravel-image-upload/public/images/icon_size/删除方法接收文件名并检查数据库中是否包含,然后在对应文件在是否存在于指定目录,如果两项都符合则该图片会被删除。
想要更加轻松地使用DropzoneJS实现图片上传,可以使用Laravel扩展包 Dropzoner 。
声明:本文为译文,原文: https://tuts.codingo.me/laravel-5-1-and-dropzone-js-auto-image-uploads-with-removal-links