import 'dart:io'; import 'package:excel/excel.dart'; import '../models/program.dart'; import '../models/step.dart'; import '../services/program_service.dart'; import 'excel_template_service.dart'; /// Excel 导入服务 /// /// 解析用户填好的 .xlsx 并通过 [ProgramService] 写入数据库。 /// 模板结构与 [ExcelTemplateService] 一致:Programs + Steps 双表, /// 通过 program_code 关联。 class ExcelImportService { static final ExcelImportService instance = ExcelImportService._internal(); final ProgramService _programService = ProgramService.instance; ExcelImportService._internal(); /// 从 .xlsx 文件导入程序,返回成功处理的程序数量(新建 + 覆盖) /// /// 行为: /// - code 不存在:新建程序 + 写入步骤 /// - code 已存在:全量覆盖程序字段,并删除旧步骤后写入新步骤 Future importFromExcel(File file) async { final bytes = await file.readAsBytes(); final excel = Excel.decodeBytes(bytes); final programsSheet = _findSheet(excel, ExcelTemplateService.sheetPrograms); if (programsSheet == null) { throw const ExcelImportException('缺少 Programs 工作表'); } final programs = _parsePrograms(programsSheet); if (programs.isEmpty) { throw const ExcelImportException('Programs 表无有效数据'); } // 已有 code → 完整 Program(用于覆盖时取 id) final existing = await _programService.getAllPrograms(); final existingByCode = { for (final p in existing) p.code: p, }; // 解析步骤 final stepsSheet = _findSheet(excel, ExcelTemplateService.sheetSteps); final stepsByCode = stepsSheet == null ? >{} : _parseSteps(stepsSheet); int processedCount = 0; for (final program in programs) { try { final existingProgram = existingByCode[program.code]; final int programId; if (existingProgram == null) { programId = await _programService.addProgram(program); } else { // 全量覆盖:保留 id,其余字段取 Excel programId = existingProgram.id!; await _programService.updateProgram( existingProgram.copyWith( name: program.name, temperature: program.temperature, airflowTime: program.airflowTime, status: program.status, createdAt: program.createdAt, ), ); // 清空旧步骤 final oldSteps = await _programService.getStepsByProgramId(programId); if (oldSteps.isNotEmpty) { await _programService.deleteSteps( oldSteps.map((s) => s.id!).toList(), ); } } // 写入新步骤(按 step_no 排序后重新编号为 1..N) final rawSteps = stepsByCode[program.code] ?? const <_RawStep>[]; rawSteps.sort((a, b) => a.stepNo.compareTo(b.stepNo)); for (var i = 0; i < rawSteps.length; i++) { final raw = rawSteps[i]; final step = Step( programId: programId, stepNo: i + 1, position: raw.position, name: raw.name, mixTime: raw.mixTime, magnetTime: raw.magnetTime, volume: raw.volume, blowTime: raw.blowTime, speed: raw.speed, ); await _programService.addStep(step); } processedCount++; } catch (_) { // 单条失败不影响其他程序 continue; } } return processedCount; } Sheet? _findSheet(Excel excel, String name) { for (final entry in excel.tables.entries) { if (entry.key == name) return entry.value; } return null; } /// 解析 Programs 表 /// 返回不含 id 的 Program 列表(createdAt 由调用方填充) List _parsePrograms(Sheet sheet) { final rows = _dataRows(sheet); final results = []; for (final cells in rows) { final code = _readString(cells, 0); final name = _readString(cells, 1); if (code.isEmpty || name.isEmpty) continue; results.add( Program( code: code, name: name, createdAt: DateTime.now().toString().split('.').first, status: _readInt(cells, 4, 1) == 0 ? 0 : 1, temperature: _readInt(cells, 2, 50), airflowTime: _readInt(cells, 3, 60), ), ); } return results; } /// 解析 Steps 表 /// 以 program_code 为 key 分组 Map> _parseSteps(Sheet sheet) { final rows = _dataRows(sheet); final map = >{}; for (final cells in rows) { final programCode = _readString(cells, 0); if (programCode.isEmpty) continue; final position = _readString(cells, 2, 'A1'); final name = _readString(cells, 3); if (name.isEmpty) continue; final raw = _RawStep( stepNo: _readInt(cells, 1, 0), position: position, name: name, mixTime: _readInt(cells, 4, 0), magnetTime: _readInt(cells, 5, 0), volume: _readInt(cells, 6, 0), blowTime: _readInt(cells, 7, 0), speed: _readInt(cells, 8, 5), ); map.putIfAbsent(programCode, () => []).add(raw); } return map; } /// 跳过表头和说明行(首行表头,后续以"说明"开头的视为说明) List> _dataRows(Sheet sheet) { final result = >[]; for (var i = 1; i < sheet.rows.length; i++) { final row = sheet.rows[i]; // 跳过空行 if (row.every((c) => c == null || c.value == null)) continue; final firstCell = _asTrimmed(row.first?.value); if (firstCell.startsWith('说明')) continue; result.add(row.map((c) => c?.value).toList(growable: false)); } return result; } String _readString(List row, int index, [String fallback = '']) { if (index >= row.length) return fallback; final v = row[index]; if (v == null) return fallback; final s = v.toString().trim(); return s.isEmpty ? fallback : s; } int _readInt(List row, int index, int fallback) { if (index >= row.length) return fallback; final v = row[index]; if (v == null) return fallback; if (v is int) return v; if (v is double) return v.toInt(); final parsed = int.tryParse(v.toString().trim()); return parsed ?? fallback; } String _asTrimmed(Object? v) { if (v == null) return ''; return v.toString().trim(); } } class _RawStep { final int stepNo; final String position; final String name; final int mixTime; final int magnetTime; final int volume; final int blowTime; final int speed; _RawStep({ required this.stepNo, required this.position, required this.name, required this.mixTime, required this.magnetTime, required this.volume, required this.blowTime, required this.speed, }); } /// Excel 导入异常 class ExcelImportException implements Exception { final String message; const ExcelImportException(this.message); @override String toString() => message; }