
本文详解如何在 laravel excel 导入过程中,正确遍历 excel 多工作表,将每个工作表的名称(如 'banana'、'strawberry')作为字段写入数据库,避免重复插入或仅取首表名的问题。
本文详解如何在 laravel excel 导入过程中,正确遍历 excel 多工作表,将每个工作表的名称(如 'banana'、'strawberry')作为字段写入数据库,避免重复插入或仅取首表名的问题。
在使用 Laravel Excel(基于 PhpSpreadsheet)处理多工作表 Excel 文件时,一个常见需求是:每张工作表代表一类产品(如 banana 表存放香蕉数据,strawberry 表存放草莓数据),导入时需将工作表名称自动映射为数据库中的 product 字段值。但原始代码中嵌套循环顺序错误(先遍历行、再遍历表名),导致每行数据被重复插入 N 次,且所有记录的 product 值均来自同一张表(实际是内层循环末次赋值覆盖所致)。
✅ 正确逻辑:先按工作表分组,再逐表读取数据
核心原则是:以工作表为单位进行处理。不应预先加载全部工作表名后与行数据做笛卡尔积,而应——
- 使用 PhpOffice\PhpSpreadsheet\IOFactory 加载文件;
- 获取所有工作表名;
- 遍历每个工作表 → 读取其独立数据 → 批量写入对应 product 名称。
以下是重构后的 ProductImport 类实现(兼容 Laravel Excel v3+,适用于 Artisan 命令):
Excel 数据可视化图表生成器 - 把 Excel/表格数据一键转为柱状图、折线图、饼图、雷达图、堆叠柱状图、散点图、面积图、环形图、气泡图、极坐标图、水平柱状图、柱+线组合双轴图。支持数据透视后绘图(--pivot),输出交互式 HTML(可悬停查看数值、一键下载 PNG)与静态 PNG(可嵌入文档/PPT)。提供两套配色方案: cn-stock(涨红跌绿, 金融财报默认) 与 vibrant(明亮风格, 适合演示报告)。当用户提到数据可视化、画图、图表、走势图、占比图、趋势图、对比图、透视表、Exc
<?php
namespace App\Imports;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithProgressBar;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\Importable;
use App\Models\Product; // 假设模型名为 Product
class ProductImport implements ToCollection, WithHeadingRow, WithProgressBar
{
use Importable;
protected $inputFileName;
public function __construct(string $inputFileName = null)
{
$this->inputFileName = $inputFileName ?? storage_path('app/fruit/listFruit.xlsx');
}
public function collection(Collection $rows)
{
// ❌ 错误:不在此处读取全局 rows!它只反映第一个工作表(默认行为)
// ✅ 正确:绕过 Laravel Excel 默认行读取,直接用 PhpSpreadsheet 控制工作表遍历
$reader = IOFactory::createReader('Xlsx');
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load($this->inputFileName);
$sheetNames = $spreadsheet->getSheetNames();
foreach ($sheetNames as $sheetIndex => $sheetName) {
$sheet = $spreadsheet->getSheet($sheetIndex);
// 读取当前工作表全部数据(跳过标题行)
$sheetData = $sheet->toArray(null, true, true, true);
// 跳过空表或无数据表
if (empty($sheetData) || count($sheetData) <= 1) {
continue;
}
// 提取标题行(第0行),用于键名映射
$headers = array_map('strtolower', $sheetData[0]);
array_shift($sheetData); // 移除标题行
// 逐行插入,绑定当前 sheetName 到 product 字段
foreach ($sheetData as $row) {
if (empty(array_filter($row))) continue; // 跳过空行
$data = array_combine($headers, $row) + ['product' => $sheetName];
Product::updateOrCreate(
[
'price' => $data['price'] ?? null,
'size' => $data['size'] ?? null,
'product' => $sheetName, // 关键:固定为当前表名
],
[
'price' => $data['price'] ?? null,
'size' => $data['size'] ?? null,
// 其他字段...
]
);
}
}
}
}? Artisan 命令调用优化
更新 UploadProduct 命令,显式传入文件路径并启用进度条:
// app/Console/Commands/UploadProduct.php
public function handle()
{
$this->output->title('Starting multi-sheet product import');
$filePath = storage_path('app/fruit/listFruit.xlsx');
if (!file_exists($filePath)) {
$this->error("Excel file not found: {$filePath}");
return 1;
}
(new ProductImport($filePath))
->withOutput($this->output)
->import($filePath);
$this->output->success('✅ Import completed successfully for all sheets.');
}⚠️ 关键注意事项
- 不要混合 Laravel Excel 的 collection() 回调与手动 PhpSpreadsheet 加载:原代码中 collection($rows) 的 $rows 仅来自首个工作表,后续手动加载全表属于冗余且易错操作。应完全放弃 $rows 参数,专注用 PhpSpreadsheet 精确控制多表读取。
- 工作表索引 vs 名称:$spreadsheet->getSheet($index) 比 getSheetByName($name) 更可靠,避免名称含空格/特殊字符时匹配失败。
- 内存优化:对超大 Excel,建议使用 ChunkReading 或流式读取(WithChunkReading),但需配合自定义 Reader 实现。
- 数据清洗:示例中已加入空行跳过、标题小写统一等健壮性处理,生产环境应补充字段验证(如 price 必须为数字)。
✅ 最终效果
执行 php artisan fruit:productList 后:
- banana 工作表中所有行 → product = 'banana'
- strawberry 工作表中所有行 → product = 'strawberry'
每条记录准确归属其来源工作表,无交叉污染,符合业务图示预期(What should come out)。
通过重构循环结构与数据流,你不仅解决了当前问题,更掌握了 Laravel 中多工作表 Excel 导入的标准化模式——以工作表为上下文单位,驱动数据映射与持久化。

















