feat(programs): Excel 导入改为全量覆盖模式

已存在 code 的程序不再跳过,而是:
- 用 Excel 中的字段更新 program(保留 id)
- 删除该 program 的全部旧步骤
- 按 Excel 中的步骤重新写入

返回值变量名 importedCount -> processedCount 更准确。
Toast 文案同步:成功处理 / Excel 无有效数据。
This commit is contained in:
Developer
2026-06-04 15:51:03 +08:00
parent cbe1e6b470
commit 55bdaa9211
2 changed files with 39 additions and 13 deletions

View File

@@ -441,9 +441,9 @@ class _ProgramsPageState extends ConsumerState<ProgramsPage> {
if (!context.mounted) return; if (!context.mounted) return;
if (importedCount > 0) { if (importedCount > 0) {
ToastService.showSuccess(context, '成功导入 $importedCount 个程序'); ToastService.showSuccess(context, '成功处理 $importedCount 个程序');
} else { } else {
ToastService.showWarning(context, '未导入新程序(编号可能已存在)'); ToastService.showWarning(context, 'Excel 中无有效程序数据');
} }
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!context.mounted) return;

View File

@@ -18,7 +18,11 @@ class ExcelImportService {
ExcelImportService._internal(); ExcelImportService._internal();
/// 从 .xlsx 文件导入程序,返回成功导入的程序数量 /// 从 .xlsx 文件导入程序,返回成功处理的程序数量(新建 + 覆盖)
///
/// 行为:
/// - code 不存在:新建程序 + 写入步骤
/// - code 已存在:全量覆盖程序字段,并删除旧步骤后写入新步骤
Future<int> importFromExcel(File file) async { Future<int> importFromExcel(File file) async {
final bytes = await file.readAsBytes(); final bytes = await file.readAsBytes();
final excel = Excel.decodeBytes(bytes); final excel = Excel.decodeBytes(bytes);
@@ -33,9 +37,11 @@ class ExcelImportService {
throw const ExcelImportException('Programs 表无有效数据'); throw const ExcelImportException('Programs 表无有效数据');
} }
// 读取已有 code,避免重复 // 已有 code → 完整 Program用于覆盖时取 id
final existing = await _programService.getAllPrograms(); final existing = await _programService.getAllPrograms();
final existingCodes = existing.map((p) => p.code).toSet(); final existingByCode = <String, Program>{
for (final p in existing) p.code: p,
};
// 解析步骤 // 解析步骤
final stepsSheet = _findSheet(excel, ExcelTemplateService.sheetSteps); final stepsSheet = _findSheet(excel, ExcelTemplateService.sheetSteps);
@@ -43,17 +49,37 @@ class ExcelImportService {
? <String, List<_RawStep>>{} ? <String, List<_RawStep>>{}
: _parseSteps(stepsSheet); : _parseSteps(stepsSheet);
int importedCount = 0; int processedCount = 0;
for (final program in programs) { for (final program in programs) {
try { try {
if (existingCodes.contains(program.code)) { final existingProgram = existingByCode[program.code];
continue; 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(),
);
}
} }
final programId = await _programService.addProgram(program); // 写入新步骤(按 step_no 排序后重新编号为 1..N
final rawSteps = stepsByCode[program.code] ?? const <_RawStep>[]; final rawSteps = stepsByCode[program.code] ?? const <_RawStep>[];
// 按 step_no 排序后写入
rawSteps.sort((a, b) => a.stepNo.compareTo(b.stepNo)); rawSteps.sort((a, b) => a.stepNo.compareTo(b.stepNo));
for (var i = 0; i < rawSteps.length; i++) { for (var i = 0; i < rawSteps.length; i++) {
final raw = rawSteps[i]; final raw = rawSteps[i];
@@ -71,14 +97,14 @@ class ExcelImportService {
await _programService.addStep(step); await _programService.addStep(step);
} }
importedCount++; processedCount++;
} catch (_) { } catch (_) {
// 单条失败不影响其他程序 // 单条失败不影响其他程序
continue; continue;
} }
} }
return importedCount; return processedCount;
} }
Sheet? _findSheet(Excel excel, String name) { Sheet? _findSheet(Excel excel, String name) {