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

Laravel 9中如何实现多图上传预览及裁剪功能?

Laravel 9 + Repository模式下多图上传、裁剪、双图保存实现思路

一、前端实现(多图选择、预览、裁剪)

核心用cropper.js处理裁剪逻辑,配合原生File API实现预览:

  • 多图选择:用input[type="file"]开启multiple属性,监听change事件获取选中的文件列表
  • 批量预览与裁剪容器:遍历选中的文件,为每个文件创建独立的预览区和裁剪容器,用URL.createObjectURL生成预览地址
  • 裁剪参数收集:用户调整裁剪区域后,通过Cropper实例的getData()获取裁剪参数(x、y、width、height、rotate等),或直接获取裁剪后的Blob/Base64
  • 表单提交:用FormData封装所有原图File对象和对应的裁剪参数,提交到后端接口

示例前端代码片段:

// 初始化全局FormData
window.uploadFormData = new FormData();

// 监听文件选择
document.getElementById('image-upload').addEventListener('change', function(e) {
  const files = e.target.files;
  const container = document.getElementById('crop-container');
  
  Array.from(files).forEach(file => {
    // 创建裁剪容器
    const cropItem = document.createElement('div');
    cropItem.className = 'crop-item p-2 border mb-3';
    cropItem.innerHTML = `
      <img class="crop-image w-full" src="${URL.createObjectURL(file)}" />
      <button class="save-crop mt-2 btn btn-primary">确认裁剪</button>
    `;
    container.appendChild(cropItem);

    // 初始化Cropper
    const img = cropItem.querySelector('.crop-image');
    const cropper = new Cropper(img, {
      aspectRatio: 1, // 自定义比例,可按需修改
      viewMode: 1,
      autoCropArea: 0.8,
      dragMode: 'move'
    });

    // 收集裁剪参数并绑定到FormData
    cropItem.querySelector('.save-crop').addEventListener('click', () => {
      const cropData = cropper.getData();
      const fileIndex = window.uploadFormData.getAll('images[][original]').length;
      
      window.uploadFormData.append(`images[${fileIndex}][original]`, file);
      window.uploadFormData.append(`images[${fileIndex}][crop][x]`, Math.round(cropData.x));
      window.uploadFormData.append(`images[${fileIndex}][crop][y]`, Math.round(cropData.y));
      window.uploadFormData.append(`images[${fileIndex}][crop][width]`, Math.round(cropData.width));
      window.uploadFormData.append(`images[${fileIndex}][crop][height]`, Math.round(cropData.height));
      
      cropper.destroy();
      cropItem.innerHTML = '<p class="text-success">已完成裁剪配置</p>';
    });
  });
});

// 提交表单
document.getElementById('submit-form').addEventListener('click', () => {
  fetch('/upload-images', {
    method: 'POST',
    headers: {
      'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
    },
    body: window.uploadFormData
  }).then(res => res.json())
    .then(data => console.log('上传成功:', data))
    .catch(err => console.error('上传失败:', err));
});

二、后端处理(接收、裁剪、保存)

1. 依赖准备

安装图片处理库intervention/image:

composer require intervention/image

Laravel 9会自动发现服务提供者,若未生效可手动在config/app.php中添加:

'providers' => [
    // ...
    Intervention\Image\ImageServiceProvider::class,
],
'aliases' => [
    // ...
    'Image' => Intervention\Image\Facades\Image::class,
]

2. Controller逻辑

接收FormData,循环处理每个图片,保存原图+生成裁剪图:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\Interfaces\ImageRepositoryInterface;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Storage;

class ImageUploadController extends Controller
{
    private $imageRepository;

    // 依赖注入Repository接口
    public function __construct(ImageRepositoryInterface $imageRepository)
    {
        $this->imageRepository = $imageRepository;
    }

    public function store(Request $request)
    {
        // 验证请求参数
        $request->validate([
            'images.*.original' => 'required|image|max:2048', // 限制单图2M以内
        ]);

        $uploadedRecords = [];

        foreach ($request->images as $index => $imageData) {
            $originalFile = $request->file("images.$index.original");
            $cropParams = $imageData['crop'];

            // 1. 保存原图到指定磁盘
            $originalFileName = uniqid() . '.' . $originalFile->getClientOriginalExtension();
            $originalPath = "original/{$originalFileName}";
            Storage::disk('public')->putFileAs('original', $originalFile, $originalFileName);

            // 2. 根据裁剪参数生成并保存裁剪图
            $croppedFileName = uniqid() . '_cropped.' . $originalFile->getClientOriginalExtension();
            $croppedPath = "cropped/{$croppedFileName}";
            
            Image::make($originalFile)
                ->crop((int)$cropParams['width'], (int)$cropParams['height'], (int)$cropParams['x'], (int)$cropParams['y'])
                ->save(storage_path("app/public/{$croppedPath}"));

            // 3. 调用Repository持久化数据
            $record = $this->imageRepository->create([
                'original_path' => $originalPath,
                'cropped_path' => $croppedPath,
                'related_id' => $request->input('related_id'), // 关联业务模型ID(如文章、商品ID)
                'related_type' => $request->input('related_type'), // 关联模型类型(可选)
            ]);

            $uploadedRecords[] = $record;
        }

        return response()->json(['data' => $uploadedRecords], 201);
    }
}

三、Repository模式适配

1. 定义Repository接口

namespace App\Repositories\Interfaces;

interface ImageRepositoryInterface
{
    public function create(array $data);
    // 可扩展其他方法:如按关联模型查询、批量删除等
}

2. 实现Eloquent Repository

namespace App\Repositories;

use App\Models\Image;
use App\Repositories\Interfaces\ImageRepositoryInterface;

class EloquentImageRepository implements ImageRepositoryInterface
{
    public function create(array $data)
    {
        return Image::create($data);
    }
}

3. 绑定接口与实现

在App\Providers\AppServiceProvider的register方法中绑定:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Interfaces\ImageRepositoryInterface;
use App\Repositories\EloquentImageRepository;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(ImageRepositoryInterface::class, EloquentImageRepository::class);
    }
}

四、额外注意事项

  • 异步优化:若处理大量图片,可将裁剪+保存逻辑放入Laravel队列,避免请求超时
  • 命名规范:原图和裁剪图可采用关联命名(如original/abc123.jpg对应cropped/abc123_cropped.jpg),便于后续管理
  • 权限控制:若使用public磁盘,可通过路由中间件限制图片访问;若用私有磁盘,需编写下载控制器返回图片
  • 前端体验:添加上传进度条、裁剪预览实时更新、错误提示等交互优化

内容的提问来源于stack exchange,提问作者Babur Shah Sayer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 18:31:03