You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 7控制器中拖拽方式收发FileList的多文件上传问题

多文件拖拽上传与Rails 7后端接收的正确实现

问题重现

现有如下表单代码:

<form id="add_content" enctype="multipart/form-data" action="/contents/create" accept-charset="UTF-8" method="post">
    <input type="hidden" name="authenticity_token" value="*****" autocomplete="off" />
    <input type="hidden" value="*****" id="database_id" name="database_id">
    <div id="drop-area">
        <input type="file" id="file_list" name="file_list" multiple>
    </div>
</form>

拖拽处理JS代码:

let dropArea = document.getElementById('drop-area')

dropArea.addEventListener('drop', handleDrop, false)

function handleDrop(e) {
    let dt    = e.dataTransfer
    let files = dt.files

    handleFiles(files)
}

function handleFiles(files) {
    let form = document.getElementById('add_content')
    console.log(files)
    let file_list = document.getElementById('file_list')
    console.log(file_list.files.length)
    form.submit();
}

遇到的问题:

  • 拖拽两个文件后,控制台能看到包含两个文件的FileList,但file_list.files.length输出0,提交后Rails控制器params[:file_list]为NilClass
  • 尝试file_list.files = files后,控制台显示file_list.files.length为2,但后端仅收到最后一个文件,调用params[:file_list].length时提示方法未定义

解决方案

前端修正:用FormData提交拖拽文件

input[type=file]的files属性是只读的,无法直接赋值。需通过FormData构建包含所有表单数据和拖拽文件的请求体,步骤如下:

  1. 完善拖拽事件处理,阻止默认行为:
let dropArea = document.getElementById('drop-area')

// 添加dragover事件,阻止默认行为并标记拖拽状态
dropArea.addEventListener('dragover', (e) => {
    e.preventDefault()
    e.stopPropagation()
    dropArea.classList.add('dragover') // 可选,添加视觉反馈样式
})

// 添加dragleave事件,移除拖拽状态
dropArea.addEventListener('dragleave', (e) => {
    e.preventDefault()
    e.stopPropagation()
    dropArea.classList.remove('dragover')
})

dropArea.addEventListener('drop', handleDrop, false)

function handleDrop(e) {
    e.preventDefault()
    e.stopPropagation()
    dropArea.classList.remove('dragover')
    
    let dt = e.dataTransfer
    let files = dt.files
    handleFiles(files)
}
  1. 修改handleFiles函数,用FormData提交:
function handleFiles(files) {
    const form = document.getElementById('add_content')
    // 基于原表单创建FormData,自动包含authenticity_token、database_id等字段
    const formData = new FormData(form)
    
    // 遍历拖拽的文件,添加到FormData,字段名加[]告知后端这是数组
    for (let i = 0; i < files.length; i++) {
        formData.append('file_list[]', files[i])
    }

    // 用fetch提交请求
    fetch(form.action, {
        method: form.method,
        body: formData,
        headers: {
            // 手动带上CSRF Token,FormData不会自动包含
            'X-CSRF-Token': document.querySelector('input[name="authenticity_token"]').value
        }
    })
    .then(res => res.json()) // 根据后端响应格式调整
    .then(data => console.log('上传成功:', data))
    .catch(err => console.error('上传失败:', err))
}

Rails后端处理:接收数组形式的文件参数

前端用file_list[]作为字段名提交后,后端params[:file_list]会是一个包含所有上传文件的数组,每个元素是ActionDispatch::Http::UploadedFile对象:

def create
    # 检查是否有上传文件
    if params[:file_list].present?
        params[:file_list].each do |uploaded_file|
            # 处理单个文件,比如保存到Active Storage或本地
            # 示例:
            # Content.create(
            #   database_id: params[:database_id],
            #   file: uploaded_file
            # )
        end
        redirect_to contents_path, notice: '所有文件上传成功'
    else
        redirect_to new_content_path, alert: '请选择要上传的文件'
    end
end

关键原因说明

  1. 直接修改input.files无效:浏览器限制了FileList对象的可写性,只能通过用户手动选择文件或特定API修改
  2. 原表单提交未携带拖拽文件:拖拽的文件未关联到表单的input控件,默认提交时只会携带input已选中的文件(此处为0个)
  3. file_list[]字段名:告知Rails将该参数解析为数组,而非单个文件,避免覆盖最后一个文件

内容的提问来源于stack exchange,提问作者icalvete

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 01:55:54