欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

Laravel 5.1 中基于 Dropzone.js 实现图片拖拽上传及删除功能

程序员文章站 2022-03-27 15:55:14
...
注:本教程代码适用于Laravel 5.1版本。

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('content')
@yield('footer')

接下来我们创建上传图片的视图文件 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')    

Images


{!! 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
@stop

在上传视图的 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