
本文详解如何在 Laravel 中使用 Laravel Excel 和 PhpSpreadsheet,按 Excel 多工作表结构导入数据,并将每张工作表的名称(如 banana、strawberry)作为字段写入数据库,避免所有记录误填为同一张表名。
本文详解如何在 laravel 中使用 laravel excel 和 phpspreadsheet,按 excel 多工作表结构导入数据,并将每张工作表的名称(如 `banana`、`strawberry`)作为字段写入数据库,避免所有记录误填为同一张表名。
在使用 Laravel Excel 进行多工作表(Sheet)导入时,一个常见误区是:在 collection() 方法中错误嵌套循环顺序,导致所有行数据都被重复写入并绑定到第一个工作表名。核心问题在于:你当前的逻辑是「对每一行 → 遍历所有 Sheet 名」,这会为单行生成 N 条记录(N = Sheet 数量),且 updateOrCreate 无法区分来源 Sheet。
✅ 正确逻辑应是:先遍历每个 Sheet → 再读取该 Sheet 的全部数据行 → 批量写入,且每条记录携带当前 Sheet 名。
以下是优化后的完整实现方案(兼容 Laravel Excel v3.1+ 及 PhpSpreadsheet):
✅ 正确做法:按 Sheet 分层处理数据
<?php
namespace App\Imports;
use App\Models\Product;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\WithProgressBar;
use Maatwebsite\Excel\Concerns\ToCollection;
use PhpOffice\PhpSpreadsheet\IOFactory;
class ProductImport implements ToCollection, WithProgressBar
{
use Importable;
public function collection(Collection $rows)
{
// ❌ 不要在此处重新加载文件!$rows 是默认第一个 Sheet 的数据(干扰源)
// ✅ 改为:手动加载整个 Excel,按 Sheet 逐个解析
$filePath = storage_path('app/fruit/listFruit.xlsx');
$reader = IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($filePath);
$sheetNames = $spreadsheet->getSheetNames();
foreach ($sheetNames as $sheetName) {
// 获取当前 Sheet 对象
$sheet = $spreadsheet->getSheetByName($sheetName);
if (!$sheet) continue;
// 转为二维数组(跳过标题行,保留键名映射)
$data = $sheet->toArray(null, true, true, true);
// 假设第 1 行是标题(header),从第 2 行开始为数据
$headers = array_shift($data); // 提取表头
if (empty($headers)) continue;
// 将每行数据与表头映射为关联数组
$mappedRows = [];
foreach ($data as $row) {
$mappedRows[] = array_combine($headers, $row) ?: [];
}
// 批量插入或更新,每条记录绑定当前 sheetName(即 product 名)
foreach ($mappedRows as $row) {
if (empty($row['price']) || empty($row['size'])) {
continue; // 跳过空行
}
Product::updateOrCreate(
[
'price' => $row['price'],
'size' => $row['size'],
// ✅ 关键:此处绑定的是当前 Sheet 名,非固定值
'product' => $sheetName,
],
[
'price' => $row['price'],
'size' => $row['size'],
'product' => $sheetName,
]
);
}
}
}
}⚠️ 注意事项与最佳实践
- 不要混用 Laravel Excel 自动解析与 PhpSpreadsheet 手动解析:原代码中 $rows 是 Laravel Excel 默认读取的第一个 Sheet 数据,而你又手动加载了整个文件——这会造成逻辑冲突和性能浪费。应完全弃用 $rows 参数,改由 IOFactory 统一控制读取。
- Sheet 名即业务标识:确保 Excel 中每个 Sheet 名(如 banana)与数据库 product 字段语义一致,且符合唯一性约束(如需 unique:products,product 验证,请在 Model 中配置)。
- 内存与性能:若 Sheet 数据量极大(>10,000 行),建议改用 WithChunkReading + 手动分块读取,或使用 DB::transaction() + upsert() 提升效率。
-
错误处理增强(推荐补充):
try { // ... 上述导入逻辑 } catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) { \Log::error('Excel read failed: ' . $e->getMessage()); throw $e; }
✅ 最终效果验证
执行命令后,数据库将正确写入: | id | price | size | product | |----|--------|------|-------------| | 1 | 1.20 | M | banana | | 2 | 0.85 | S | banana | | 3 | 3.50 | L | strawberry | | 4 | 2.99 | M | strawberry |
每条记录的 product 字段严格对应其原始所在 Sheet 名称,彻底解决“只存第一个 Sheet 名”的问题。
? 提示:如需进一步解耦(如不同 Sheet 结构不同),可结合 WithCustomValueBinder 或自定义 SheetImport 类实现策略模式导入。


















