import( $file, (int) (company_id() ?: 1), ); } $spreadsheet = IOFactory::load($file->getRealPath()); [$rows, $headerRow, $columnMap] = $this->bestMatchingSheet($spreadsheet, $descriptor); if ($rows === null || $columnMap === []) { throw ValidationException::withMessages([ 'import' => '未找到可识别的表头,请使用当前页面显示的中文表头。', ]); } $created = 0; $updated = 0; $model = $descriptor->model; $identityFields = TableRegistry::importIdentityFields($section, $page); DB::transaction(function () use ( $rows, $headerRow, $columnMap, $descriptor, $section, $page, $tab, $model, $identityFields, &$created, &$updated, ) { foreach (array_slice($rows, $headerRow + 1) as $offset => $row) { $data = []; foreach ($columnMap as $columnIndex => $field) { $data[$field['name']] = $this->normalizeValue( $row[$columnIndex] ?? null, $field['type'], $headerRow + $offset + 2, $field['label'], ); } if (collect($data)->every(fn ($value) => $value === null || $value === '')) { continue; } if ($descriptor->tabFilterColumn && $tab) { $tabValue = TableRegistry::tabValue($section, $page, $tab); if ($tabValue !== null) { $data[$descriptor->tabFilterColumn] = $tabValue; } } foreach (TableRegistry::fixedFilters($section, $page) as $column => $value) { $data[$column] = $value; } $data['company_id'] = company_id() ?: 1; $identity = collect($identityFields) ->filter(fn ($field) => array_key_exists($field, $data) && $data[$field] !== null && $data[$field] !== '') ->mapWithKeys(fn ($field) => [$field => $data[$field]]) ->all(); $record = count($identity) === count($identityFields) && $identity !== [] ? $model::query()->where($identity)->first() : null; if ($record) { $record->update($data); $updated++; } else { $model::create($data); $created++; } } }); if ($created + $updated === 0) { throw ValidationException::withMessages([ 'import' => '表格中没有可导入的数据行。', ]); } return compact('created', 'updated'); } private function bestMatchingSheet($spreadsheet, TableDescriptor $descriptor): array { $fieldLookup = []; foreach ($descriptor->fields as $field) { $fieldLookup[$this->normalizeHeader($field['name'])] = $field; $fieldLookup[$this->normalizeHeader($field['label'])] = $field; } $best = [null, null, []]; $bestScore = 0; foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { $rows = $worksheet->toArray(null, true, false, false); foreach (array_slice($rows, 0, 20, true) as $rowIndex => $row) { $columnMap = []; foreach ($row as $columnIndex => $heading) { $key = $this->normalizeHeader($heading); if ($key !== '' && isset($fieldLookup[$key])) { $columnMap[$columnIndex] = $fieldLookup[$key]; } } if (count($columnMap) > $bestScore) { $best = [$rows, $rowIndex, $columnMap]; $bestScore = count($columnMap); } } } return $best; } private function normalizeHeader($value): string { return mb_strtolower(preg_replace('/[\s\x{FEFF}_\-()()]+/u', '', trim((string) $value))); } private function normalizeValue($value, string $type, int $rowNumber, string $label) { if ($value === null || trim((string) $value) === '') { return null; } if ($type === 'date') { try { return is_numeric($value) ? ExcelDate::excelToDateTimeObject($value)->format('Y-m-d') : \Illuminate\Support\Carbon::parse($value)->format('Y-m-d'); } catch (\Throwable $e) { throw ValidationException::withMessages([ 'import' => "第 {$rowNumber} 行“{$label}”不是有效日期。", ]); } } if (in_array($type, ['number', 'currency'], true)) { $number = str_replace([',', ',', '¥', '¥'], '', (string) $value); if (! is_numeric($number)) { throw ValidationException::withMessages([ 'import' => "第 {$rowNumber} 行“{$label}”不是有效数字。", ]); } return (float) $number; } if ($type === 'boolean') { return in_array(mb_strtolower(trim((string) $value)), ['1', '是', 'true', 'yes', 'y'], true) ? 1 : 0; } return trim((string) $value); } }