将区域表单拖放到全局表单中:无法使用 Laravel 获取文件

Dropzone form into a global form : impossible to get the file with Laravel

我正在尝试在我网站的主页上创建一个表单,其中包含一个带有预览的拖放区区域,以查看具有其他三个输入的文件。目标是在验证时,所有内容都存储在数据库中,并且我能够在另一页上打开文档。

问题是,无论我尝试什么,一旦文件加载 Dropzone.JS,我似乎无法在服务器端获取文件 Dropzone.JS。

每次在服务器端我都能获得我的表单数据,但不能获得文件本身,这会在服务器(控制器)端给我一个错误。

此外,还有一个问题是进度条是空的,提交表单后甚至没有填写。最好是它加载文件(将进度条设置为 100%),然后一旦它 "loaded",我就可以将它与我的表单的其余部分一起提交。

我正在使用 Bootstrap 实现提供的部分代码:dropzone-bootstrap.

这是我的 HTML 表格:

<form method="post" action="/upload-free-document" id="send-files-form" files="true" enctype="multipart/form-data">
<h3 class="form-title text-left">@lang('landing.get_started')</h3>
{{ csrf_field() }}
<div class="form-header">
    <div class="form-group icon-addon addon-lg">
        <input type="text" name="email" id="email" class="form-control wow fadeInUp" placeholder="@lang('landing.your_email')" required>
        <label for="email" class="glyphicon glyphicon-user wow fadeInUp" rel="tooltip" title="@lang('landing.your_email')"></label>
    </div>
    <div class="form-group icon-addon addon-lg">
        <input type="text" name="signatories-email" id="signatories-email" class="form-control wow fadeInUp" placeholder="@lang('landing.signatories_email')" required>
        <label for="email" class="glyphicon glyphicon-envelope wow fadeInUp" rel="tooltip" title="@lang('landing.your_email')"></label>
    </div>
    <div class="form-group">
        <div class="col-md-12 input-group">
            <span class="input-group-addon message-icon wow fadeInUp"><span class="glyphicon glyphicon-pencil message-glyph-icon"></span></span>
            <textarea id="document-message" name="document-message" rows="10" cols="20" class="form-control input-message wow fadeInUp" maxlength="1500" placeholder="@lang('landing.your_message')" required></textarea>
        </div>
        <span class="caracters-left"><span id="chars">1500</span> @lang('landing.caracters_left')</span>
    </div>

    <div id="actions" class="row">
        <div class="col-lg-6">
            <span class="btn btn-success fileinput-button upload-buttons add-file-button">
                <i class="glyphicon glyphicon-plus"></i>
                <span>@lang('landing.add_file')</span>
            </span>
        </div>
    </div>
    <div class="table table-striped files" id="previews">
      <div id="template" class="file-row">
        <div>
            <span class="preview"><img data-dz-thumbnail /></span>
        </div>
        <div>
            <p class="name" data-dz-name></p>
            <strong class="error text-danger" data-dz-errormessage></strong>
        </div>
        <div>
            <p class="size" data-dz-size></p>
            <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
              <div class="progress-bar progress-bar-success" style="width:0%;" data-dz-uploadprogress></div>
            </div>
        </div>
      </div>
    </div>

    <div class="form-group last">
        <input type="submit" id="submit-document" class="btn btn-warning btn-block btn-lg" value="@lang('landing.demo_sign_document')">
    </div>
    <p class="privacy text-center">@lang('landing.privacy_text') <a href="privacy.html">@lang('landing.privacy_link')</a>.</p>
</div>

这里是 Dropzone 配置:

    // Get the template HTML and remove it from the doumenthe template HTML and remove it from the doument
var previewNode = document.querySelector("#template");
previewNode.id = "";
var previewTemplate = previewNode.parentNode.innerHTML;
previewNode.parentNode.removeChild(previewNode);

Dropzone.autoDiscover = false;

var myDropzone = new Dropzone(document.body, { // Make the whole body a dropzone
    url: "/upload-free-document",
    thumbnailWidth: 80,
    thumbnailHeight: 80,
    parallelUploads: 20,
    previewTemplate: previewTemplate,
    autoQueue: false,
    previewsContainer: "#previews", // Define the container to display the previews
    clickable: ".fileinput-button", // Define the element that should be used as click trigger to select files.
    maxFiles: 1,
    acceptedFiles: ".pdf", //is this correct? I got an error if im using this
    maxFilesize: 3145728,
    parallelUploads: 1,
    uploadMultiple: false,
    autoProcessQueue: false,

    // The setting up of the dropzone
    init: function() {
        var myDropzone = this;

        $("#submit-document").click(function (e) {
            // e.preventDefault();
            e.stopPropagation();
            myDropzone.processQueue();
        }); 

        myDropzone.on("addedfile", function(file) {
            console.log("Fichier ajouté");
        });

        // Listen to the sendingmultiple event. In this case, it's the sendingmultiple event instead
        // of the sending event because uploadMultiple is set to true.
        this.on("sending", function(file, xhr, formData) {
            console.log(formData);
            formData.append("email", $('#email').val());
            formData.append("_token", $('[name=_token').val());
            console.log("Fichier en cours d'envoi."); 
        });

        this.on("success", function(files, response) {
            // Gets triggered when the files have successfully been sent.
            // Redirect user or notify of success
             myDropzone.removeAllFiles();
            console.log("Succès de l'envoi.");
        });

        this.on("error", function(files, response) {
            // Gets triggered when there was an error sending the files.
            // Maybe show form again, and notify user of error
            console.log("Erreur de l'envoi.");
        });
      }
});

这是上传文件的方法(该方法在另一个页面上运行,我只有 DropzoneJS 部分,而不是包含 Dropzone.JS 的整个表单)。

    /**
 * Upload the user file to the server
 *
 * @param      \Illuminate\Http\Request  $request  The request
 *
 * @return     string                    ( response message )
 */
public function upload(Request $request)
{
    $userFiles = null;
    $email = $request->input('email');

    // Check if there is already a file associated with the email 
    if ($email) 
    {
        $userFiles = Files::getNonUserFiles($email);
    }
    else 
    {
        return Response::json(array(
            'error'   =>  Lang::get('landing.no_email')
        ), 400);
    }

    // If the non user already has a file online, we block it
    if(!$userFiles->isEmpty())
    {
        return Response::json(array(
            'error'   =>  Lang::get('landing.already_have_file')
        ), 400);
    }

    // Upload settings
    $uploadSettings = uploadSettings::first();

    // If there is a file uploaded
    if ($request->file('file')->isValid())
    {
        // Getting the uploaded file
        $file = $request->file('file');

        // Get the extension of the file
        $extension = $file->getClientOriginalExtension();

        // Getting allowed extension
        $allowedExt = explode(',' , $uploadSettings->allowedFilesExt);

        // Get the file size
        $size = $file->getSize();

        // File size
        if($size > $uploadSettings->maxFreeFileSize) 
        {
            return Response::json(array(
                'error'   =>  Lang::get('documents.size_too_big')
            ), 400);
        }
        // File type
        else if( !in_array(strtolower($extension), $allowedExt) )
        {
            return Response::json(array(
                'error'   =>  Lang::get('documents.wrong_format')
            ), 400);
        }
        // If everything is all right
        else 
        {
            // Where the file will be uploaded
            $destinationPath = $this->noUserStorageService->storageDirectory();

            // Generate a new date
            $date = time('d-m-Y h:i:s.u');

            if (!preg_match('/^[\x20-\x7E]+$/', $file->getClientOriginalName() ))
            {
                $filename = str_replace(' ','',$date.'_'.generateRandomString(10).'.'.$file->getClientOriginalExtension());
            }
            else
            {
                $filename = str_replace(' ','',$date.'_'.$file->getClientOriginalName());
                $filename = str_replace("#", "_", $filename);
            }
                $path = $request->file('file')->storeAs(
                    $destinationPath, $filename
                );

                // If file Uploaded Success
                if ($path) 
                {
                    // We create the file into the database
                    $files              = new Files;

                    // File Name
                    $files->name        = pathinfo(strtolower(htmlentities($file->getClientOriginalName())), PATHINFO_FILENAME);
                    $files->server_name = $filename;
                    // File Path
                    $files->path        = preg_replace('/\s+/', '',url('/file/'.pathinfo($filename,PATHINFO_FILENAME)));

                    // File Extention
                    $files->extension   = $extension;
                    $files->user_email  = $email;

                    // File Status
                    $files->status      = 1;
                    $files->auto_sign   = 0;

                    $files->size = $file->getClientSize() ;

                    // Save File Info
                    $files->save();

                    return Response::json(array(
                        'message'   =>  'success'
                    ), 200); 
                }
                else 
                {
                    return Response::json(array(
                        'error'   =>  "Impossible d'envoyer le fichier sur le serveur. Veuillez réessayer."
                    ), 400);
                }
            }
    }   
}

我不认为它来自我的 Laravel 配置,因为 DropzoneJS 经典形式(未包含在另一种形式中)在我项目的其他地方工作正常。

我的表单或放置区配置有什么问题?

我一直在玩这个。 Bootstrap 演示对香草 Dropzone 进行的定制之一是每个单独文件的开始按钮。但是,您没有使用该开始按钮(您的模板不包括一个)。此外,您还有 maxFiles: 1parallelUploads: 1(实际上您也有 parallelUploads: 20,但第二个可能优先)。所以看起来您真的只想在此表单上上传 1 个文件,对吗?在这种情况下,为什么需要这个 Bootstrap 演示方法?如果目标是调整文件的布局和外观并上传,您可以使用 previewTemplate 在 vanilla Dropzone 中完成,这更简单一些,我认为可以避免您看到的问题。

无论如何,问题的关键是在 Boostrap 演示中,开始按钮是将文件排入队列的按钮。他们的配置有 autoQueue: false,因此没有文件自动排队。相反,他们在每次添加文件时添加一个事件监听器:

file.previewElement.querySelector(".start").onclick = function() { myDropzone.enqueueFile(file); };

您也有 autoQueue: false,但您没有开始按钮,也没有手动将文件排队,因此您的文件永远不会排队。因此,当您点击提交时,Dropzone 的队列中没有任何要处理的内容,因此您在服务器端看到的只是其他表单输入值(电子邮件等)。

最简单的修复方法就是删除 autoQueue: false,这样您的文件将在 select 后立即加入队列。在对您的代码进行本地测试时,这对我有用 - 该文件包含在 POSTed 到后端的数据中。

需要注意的一件事是,当您点击提交按钮时,实际上会发生 2 个单独的 POST。首先 Dropzone 只发布文件,然后您的表单与您的文本输入一起提交。这将是另一个问题,因为看起来您的控制器目前希望同时上传表单数据和文件。

看起来您可能已经开始解决这个问题了,方法是 formData.append 将您的一些表单输入输入到 Dropzone POST。您可以为每个输入执行此操作,然后以某种方式忽略第二个 POST(或者让 action 指向不同的 Controller 方法?)。或者,您可以为每个 POST 使用单独的控制器方法 - 使用 formaction 指定保存表单数据的方法,并使用 Dropzone 的 url 配置选项指定处理文件上传的不同方法。

更新

这里有一个更清晰的示例,说明了解决这个双重 POST 问题的方法。

在您的 Javascript 中,首先将表单中的每个输入附加到 Dropzone:

this.on("sending", function(file, xhr, formData) {
    // Get every input on the form
    var data = $('#send-files-form').serializeArray();

    // Append them all to the formData Dropzone will POST
    $.each(data, function(key, el) {
        formData.append(el.name, el.value);
    });
    console.log("Fichier en cours d'envoi.");
});

现在您的所有表单数据以及文件都在 Dropzone 将执行的 POST 中。这意味着您可以忽略或禁用 Dropzone 完成后发生的第二个 POST。为此,请停止实际发布的表格:

$("#submit-document").click(function (e) {
    e.preventDefault();
    myDropzone.processQueue();
});

现在只有 1 个 POST。当然,现在您必须手动处理表单提交后的操作。您可以使用 successerror Dropzone 回调来做到这一点。您的控制器已经返回 JSON 成功或错误消息,因此也许您可以在前端显示它。在 HTML:

中添加某种消息占位符
<div class="message"></div>

并在您的回调中定位它:

this.on("success", function(files, response) {
    $('div.message').html(response.message);
    myDropzone.removeAllFiles();
    console.log("Succès de l'envoi.");
});

this.on("error", function(files, response) {
    $('div.message').html(response.error);
    console.log("Erreur de l'envoi.");
});

或者在成功提交后您可能想重定向到另一个页面:

this.on("success", function(files, response) {
    myDropzone.removeAllFiles();
    window.location('/some/other/place');
    console.log("Succès de l'envoi.");
});

小问题 - 您包含的代码缺少结束 </form> 标记,这让我有些头疼。您可能只是没有从您的代码中复制粘贴它,而是为了以防万一提及它。

最后一件事 - the docs seem to be broken on this,但是 AFAICT maxFilesize 值应该以 MB 为单位(在文档中搜索该字符串以查看一些示例)。您的 maxFilesize: 3145728 值可能应该类似于 maxFilesize: 3.