商品导入导出
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\ProductCategoryModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\SupplierModel;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
/**
|
||||
* 商品档案导出:列格式与导入模板完全一致(首行即列头,导出文件修改后可直接重新导入)。
|
||||
* 分类列输出「父分类/子分类」完整路径;状态列输出中文。
|
||||
* template 模式仅输出列头 + 一行示例数据(首次导入前下载参考)。
|
||||
*/
|
||||
class ProductExport implements FromCollection, WithStrictNullComparison, WithStyles
|
||||
{
|
||||
/** 列头(与 ProductImport 的解析顺序一一对应,改动需同步) */
|
||||
public const array HEADERS = ['品名', '分类', '供应商', '规格/包规', '单位', '成本价', '排序', '库存', '保质期', '状态', '备注'];
|
||||
|
||||
private ?Collection $rows = null;
|
||||
|
||||
/**
|
||||
* @param int $categoryId 末级分类筛选(0=全部商品)
|
||||
* @param bool $template 仅输出列头 + 示例行(导入模板)
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $categoryId = 0,
|
||||
private readonly bool $template = false,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 行结构:第 1 行列头,其后每行一件商品
|
||||
*/
|
||||
public function collection(): Collection
|
||||
{
|
||||
if ($this->rows !== null) {
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
$rows = [self::HEADERS];
|
||||
|
||||
if ($this->template) {
|
||||
$rows[] = ['西红柿', '蔬菜/茄果类', '示例供应商', '10斤/箱', '斤', 2.5, 0, 100, 3, '上架', '示例行,导入前请删除'];
|
||||
return $this->rows = collect($rows);
|
||||
}
|
||||
|
||||
$products = ProductModel::query()
|
||||
->when($this->categoryId > 0, fn ($query) => $query->where('category_id', $this->categoryId))
|
||||
->orderBy('sort')
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->makeVisible('cost_price');
|
||||
|
||||
$categoryPaths = $this->categoryPaths($products->pluck('category_id')->unique()->all());
|
||||
$supplierNames = SupplierModel::withTrashed()->pluck('name', 'id');
|
||||
|
||||
foreach ($products as $product) {
|
||||
$rows[] = [
|
||||
$product->name,
|
||||
$categoryPaths[(int) $product->category_id] ?? '',
|
||||
$supplierNames[(int) $product->supplier_id] ?? '',
|
||||
$product->spec,
|
||||
$product->unit,
|
||||
(float) $product->cost_price,
|
||||
(int) $product->sort,
|
||||
(int) $product->stock,
|
||||
(int) $product->shelf_life,
|
||||
(int) $product->status === ProductModel::STATUS_ON ? '上架' : '下架',
|
||||
$product->remark,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->rows = collect($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列头加粗、冻结首行、设置列宽
|
||||
*/
|
||||
public function styles(Worksheet $sheet): array
|
||||
{
|
||||
$this->collection();
|
||||
|
||||
$sheet->freezePane('A2');
|
||||
$widths = [20, 16, 14, 14, 8, 10, 8, 8, 8, 8, 24];
|
||||
foreach ($widths as $index => $width) {
|
||||
$sheet->getColumnDimension(Coordinate::stringFromColumnIndex($index + 1))->setWidth($width);
|
||||
}
|
||||
|
||||
return [
|
||||
1 => ['font' => ['bold' => true]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类ID => 「父分类/子分类」完整路径(沿 parent_id 上溯拼接)
|
||||
*
|
||||
* @param array<int, int> $categoryIds
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function categoryPaths(array $categoryIds): array
|
||||
{
|
||||
$categories = ProductCategoryModel::all()->keyBy('id');
|
||||
$paths = [];
|
||||
foreach ($categoryIds as $categoryId) {
|
||||
$names = [];
|
||||
$cursor = (int) $categoryId;
|
||||
$guard = 0;
|
||||
while ($cursor > 0 && $guard++ < 20) {
|
||||
$category = $categories->get($cursor);
|
||||
if ($category === null) {
|
||||
break;
|
||||
}
|
||||
array_unshift($names, $category->name);
|
||||
$cursor = (int) $category->parent_id;
|
||||
}
|
||||
$paths[(int) $categoryId] = implode('/', $names);
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user