127 lines
4.3 KiB
PHP
127 lines
4.3 KiB
PHP
<?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;
|
|
}
|
|
}
|