是否包邮厂家。空白或“-”表示否。 * * @return array */ public function read(UploadedFile $file): array { try { $reader = IOFactory::createReader(match (mb_strtolower($file->getClientOriginalExtension())) { 'xlsx' => 'Xlsx', 'xls' => 'Xls', 'csv' => 'Csv', default => throw new \RuntimeException('Unsupported workbook format.'), }); $book = $reader->load($file->getRealPath()); } catch (Throwable) { throw ValidationException::withMessages([ 'import' => '无法读取导入文件,请确认文件未损坏且格式为 xlsx、xls 或 csv。', ]); } try { [$sheet, $headerRow, $codeColumn, $fieldColumn] = $this->locateColumns($book); $highestRow = $sheet->getHighestDataRow(); if ($highestRow > 5001) { throw ValidationException::withMessages(['import' => '单次最多导入 5,000 条供应商记录,请拆分文件后重试。']); } $records = []; $lineByCode = []; for ($row = $headerRow + 1; $row <= $highestRow; $row++) { $code = $this->text($sheet, $codeColumn, $row); if (str_starts_with($code, "'")) { $code = substr($code, 1); } $field = $this->text($sheet, $fieldColumn, $row); if ($code === '' && $field === '') { continue; } if ($code === '') { throw ValidationException::withMessages(['import' => "第 {$row} 行供应商代码为空。"]); } if (mb_strlen($code) > 191) { throw ValidationException::withMessages(['import' => "第 {$row} 行供应商代码超过 191 个字符。"]); } $codeKey = mb_strtolower($code); if (isset($lineByCode[$codeKey])) { throw ValidationException::withMessages([ 'import' => "第 {$row} 行供应商代码“{$code}”与第 {$lineByCode[$codeKey]} 行重复。", ]); } $records[$code] = $this->parseFlag($field, $row); $lineByCode[$codeKey] = $row; } if ($records === []) { throw ValidationException::withMessages(['import' => '表格中没有可导入的供应商记录。']); } return $records; } finally { $book->disconnectWorksheets(); } } public function template(): StreamedResponse { return response()->streamDownload(function (): void { $book = new Spreadsheet(); $sheet = $book->getActiveSheet(); $sheet->setTitle('是否包邮厂家'); $sheet->fromArray([self::SUPPLIER_CODE_HEADER, '供应商名称', self::FIELD_HEADER], null, 'A1'); $sheet->getStyle('A1:C1')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']], 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER], 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'D8D2C7']]], ]); $sheet->getRowDimension(1)->setRowHeight(24); $sheet->freezePane('A2'); $sheet->setAutoFilter('A1:C1'); foreach (['A' => 22, 'B' => 34, 'C' => 20] as $column => $width) { $sheet->getColumnDimension($column)->setWidth($width); } $sheet->getStyle('A2:A5001')->getNumberFormat()->setFormatCode('@'); $instructions = $book->createSheet(); $instructions->setTitle('填写说明'); $instructions->fromArray([ ['项目', '说明'], [self::SUPPLIER_CODE_HEADER, '必填。请填写供应商管理页面中的供应商代码,按代码精确关联。'], [self::FIELD_HEADER, '填写“包邮”表示是包邮厂家;空白或“-”表示不是包邮厂家。'], ['兼容导出表', '也可以使用本页面导出的文件,在此列补充后直接导入。'], ['覆盖规则', '同一供应商再次导入会更新该字段;其他列不会修改。'], ], null, 'A1'); $instructions->getStyle('A1:B1')->applyFromArray([ 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']], 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '856632']], ]); $instructions->getColumnDimension('A')->setWidth(22); $instructions->getColumnDimension('B')->setWidth(76); $instructions->getStyle('A1:B5')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER)->setWrapText(true); foreach (range(1, 5) as $row) { $instructions->getRowDimension($row)->setRowHeight($row === 1 ? 24 : 34); } $book->setActiveSheetIndex(0); (new Xlsx($book))->save('php://output'); $book->disconnectWorksheets(); }, '供应商是否包邮厂家_导入模板_' . now()->format('Ymd') . '.xlsx', [ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Cache-Control' => 'private, no-store', ]); } /** @return array{0:Worksheet,1:int,2:int,3:int} */ private function locateColumns(Spreadsheet $book): array { $best = null; foreach ($book->getWorksheetIterator() as $sheet) { $rows = $sheet->toArray(null, false, false, false); foreach (array_slice($rows, 0, 20, true) as $rowIndex => $row) { $headers = []; foreach ($row as $columnIndex => $value) { $header = $this->header($value); if ($header !== '') { $headers[$header] = $columnIndex + 1; } } if (isset($headers[self::SUPPLIER_CODE_HEADER], $headers[self::FIELD_HEADER])) { $candidate = [$sheet, $rowIndex + 1, $headers[self::SUPPLIER_CODE_HEADER], $headers[self::FIELD_HEADER]]; $best = $candidate; break 2; } } } if ($best === null) { throw ValidationException::withMessages([ 'import' => '缺少表头:需要“供应商代码”和“是否包邮厂家”两列。', ]); } return $best; } private function parseFlag(string $value, int $row): bool { $normalized = mb_strtolower(preg_replace('/[\s\x{FEFF}]+/u', '', $value)); if (in_array($normalized, ['', '-', '否', '不是', '0', 'false', 'no'], true)) { return false; } if (in_array($normalized, ['包邮', '是', '是包邮厂家', '1', 'true', 'yes'], true)) { return true; } throw ValidationException::withMessages([ 'import' => "第 {$row} 行“是否包邮厂家”只能填写“包邮”,或留空表示否。", ]); } private function text(Worksheet $sheet, int $column, int $row): string { $cell = $sheet->getCell([$column, $row]); if ($cell->getDataType() === 'f') { throw ValidationException::withMessages(['import' => "第 {$row} 行含公式,请填写实际值。"]); } return trim((string) $cell->getFormattedValue()); } private function header(mixed $value): string { return str_replace([' ', ' ', "\n", "\r", "\t", "\x{FEFF}"], '', trim((string) $value)); } }