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

使用jQuery+PHP多图上传时服务器无法接收文件的问题

问题:Ajax+PHP多图上传,表单文本正常提交但服务器接收文件数组为空

我用Ajax和PHP实现多图上传功能,前端能动态生成上传框,最多支持5张图片,前端交互功能正常,表单里的文本内容也能正常提交到服务器,但PHP端接收的文件数组始终为空,排查代码没找到问题,求解决。

前端HTML代码

<div class="upload-container" id="uploadContainer">
    <div class="upload-box show">
        <span class="plus-sign">+</span>
        <input type="file" name="images[]" accept="image/*" multiple>
        <button class="delete-icon" type="button">×</button>
    </div>
</div>

修正后的前端JavaScript代码

document.addEventListener('DOMContentLoaded', function() {
const uploadContainer = document.getElementById('uploadContainer');
const imageUploadForm = document.getElementById('form-review');
let imageCount = 0;
const maxImages = 5;
let selectedFiles = []; // 新增全局数组存储选中的文件

function createUploadBox() {
    if (imageCount >= maxImages) return;

    const uploadBox = document.createElement('div');
    uploadBox.classList.add('upload-box');
    uploadBox.innerHTML = `
        <span class="plus-sign">+</span>
        <input type="file" accept="image/*" name="images[]" multiple>
        <button class="delete-icon" type="button">×</button>
    `;
    uploadContainer.appendChild(uploadBox);

    setTimeout(() => uploadBox.classList.add('show'), 10);

    attachUploadEvents(uploadBox);
}

function attachUploadEvents(uploadBox) {
    const input = uploadBox.querySelector('input[type="file"]');
    const deleteIcon = uploadBox.querySelector('.delete-icon');
    
    uploadBox.addEventListener('click', () => {
        if (uploadBox.classList.contains('filled')) return;
        input.click();
    });

    input.addEventListener('change', (event) => {
        const files = Array.from(event.target.files);
        const validFiles = files.slice(0, maxImages - imageCount);
        // 将选中的文件存入全局数组
        selectedFiles.push(...validFiles);
        
        validFiles.forEach(file => {
            const reader = new FileReader();
            reader.onload = (e) => {
                const imgBox = document.createElement('div');
                imgBox.classList.add('upload-box', 'show', 'filled');
                imgBox.innerHTML = `
                    <img src="${e.target.result}" alt="Image">
                    <button class="delete-icon" type="button">×</button>
                `;
                uploadContainer.insertBefore(imgBox, uploadContainer.lastElementChild);
                // 绑定删除事件时传入对应文件,方便后续移除
                attachDeleteEvent(imgBox, file);
                imageCount++;
                if (imageCount >= maxImages) {
                    const emptyUploadBox = document.querySelector('.upload-box:not(.filled)');
                    if (emptyUploadBox) emptyUploadBox.remove();
                }
            };
            reader.readAsDataURL(file);
        });

        input.value = '';
    });

    deleteIcon.addEventListener('click', (event) => {
        event.stopPropagation();
        event.preventDefault();
        uploadBox.classList.remove('show');
        setTimeout(() => {
            uploadBox.remove();
            imageCount--;
            ensureOneEmptyUploadBox();
        }, 300);
    });
}

window.resetUploadBoxes = function() {
    const filledUploadBoxes = document.querySelectorAll('.upload-box.filled');
    filledUploadBoxes.forEach(box => box.remove());
    imageCount = 0;
    selectedFiles = []; // 重置文件数组
    ensureOneEmptyUploadBox();
}

function attachDeleteEvent(uploadBox, file) {
        const deleteIcon = uploadBox.querySelector('.delete-icon');
        deleteIcon.addEventListener('click', (event) => {
            event.stopPropagation();
            event.preventDefault();
            uploadBox.classList.remove('show');
            setTimeout(() => {
                uploadBox.remove();
                imageCount--;
                // 从全局数组中移除当前删除的文件
                selectedFiles = selectedFiles.filter(f => f !== file);
                ensureOneEmptyUploadBox();
        }, 300);
    });
 }

function ensureOneEmptyUploadBox() {
    const emptyUploadBox = document.querySelector('.upload-box:not(.filled)');
    if (!emptyUploadBox && imageCount < maxImages) {
        createUploadBox();
    }
}

attachUploadEvents(document.querySelector('.upload-box'));

$('#button-review').on('click', function() {
   var formData = new FormData($('#form-review')[0]);

    // 清除FormData中可能存在的旧文件数据,避免重复
    formData.delete('images[]');
    // 将全局数组中的文件添加到FormData
    selectedFiles.forEach(file => {
        formData.append('images[]', file);
    });

    $.ajax({
        url: 'index.php?route=product/product/write&product_id={{ product_id }}',
        type: 'post',
        dataType: 'json',
        data: formData,
        cache: false,
        contentType: false,
        processData: false,
        beforeSend: function() {
            $('#button-review').button('loading');
        },
        complete: function() {
            $('#button-review').button('reset');
        },
        success: function(json) {
            $('.alert-dismissible').remove();

            if (json['error']) {
                $('#review').after('<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> ' + json['error'] + '</div>');
                $('html, body').animate({scrollTop: $("#review").offset().top}, 800);
            }

            if (json['success']) {
                resetUploadBoxes();
                $('#review').after('<div class="alert alert-success alert-dismissible"><i class="fa fa-check-circle"></i> ' + json['success'] + '</div>');
                $('html, body').animate({scrollTop: $("#review").offset().top}, 800);

                $('input[name=\'name\']').val('');
                $('textarea[name=\'text\']').val('');
                $('input[name=\'rating\']:checked').prop('checked', false);
            }
        },
        error: function(xhr, ajaxOptions, thrownError) {
            alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);
        }
    });
});
});

修正后的PHP代码

$files = [];
// 针对name为images[]的文件数组进行处理
if (!empty($this->request->files['images']) && is_array($this->request->files['images'])) {
   foreach ($this->request->files['images'] as $file) {
      if (!empty($file['name'])) {
        $files[] = array(
            'name' => $file['name'],
            'type' => $file['type'],
            'tmp_name' => $file['tmp_name'],
            'error' => $file['error'],
            'size' => $file['size']
        );
      }
    }
}

问题原因及解决说明

  1. 核心问题:原代码中选中文件后执行了input.value = ''清空了文件输入框,导致提交表单时遍历input获取不到文件;同时预览的图片盒子里没有文件输入控件,选中的文件根本没被加入到FormData中。
  2. 解决思路:用全局数组存储选中的所有文件,删除预览图时同步从数组移除对应文件,提交表单时统一将数组中的文件添加到FormData,确保文件能被正确传递到服务器。
  3. PHP端修正:原代码遍历整个$this->request->files,改为直接获取images[]对应的文件数组,匹配前端传递的参数名。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:15:55