Files
kuaishai2/lib/features/programs/services/excel_template_service.dart
Developer 16fbb7d54b refactor(programs): 模板保存目录改为下载目录
- getDownloadsDirectory() 优先,失败时回退到应用文档目录
- 更新 dartdoc 说明
2026-06-04 15:35:52 +08:00

137 lines
4.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:excel/excel.dart';
import 'package:path_provider/path_provider.dart';
/// Excel 模板服务
///
/// 生成 .xlsx 模板用于程序/步骤批量导入。
/// 模板包含两张表:
/// - Programs程序基础信息
/// - Steps步骤参数通过 program_code 与 Programs 关联)
class ExcelTemplateService {
static final ExcelTemplateService instance = ExcelTemplateService._internal();
ExcelTemplateService._internal();
/// 工作表名称
static const String sheetPrograms = 'Programs';
static const String sheetSteps = 'Steps';
/// 生成模板并保存到下载目录,返回文件对象
///
/// 优先使用系统下载目录;不支持时回退到应用文档目录。
Future<File> generateTemplate() async {
final excel = Excel.createExcel();
final bytes = _buildTemplateBytes(excel);
final dir = await getDownloadsDirectory() ?? await getApplicationDocumentsDirectory();
final file = File('${dir.path}/program_template.xlsx');
await file.writeAsBytes(bytes, flush: true);
return file;
}
/// 构建模板字节流(可在测试中直接调用)
List<int> _buildTemplateBytes(Excel excel) {
// 默认创建的第一个 sheet 重命名
final defaultSheet = excel.getDefaultSheet();
if (defaultSheet != null && defaultSheet != sheetPrograms) {
excel.rename(defaultSheet, sheetPrograms);
}
_writeProgramsSheet(excel);
_writeStepsSheet(excel);
return excel.encode()!;
}
void _writeProgramsSheet(Excel excel) {
final sheet = excel[sheetPrograms];
final headerStyle = CellStyle(
bold: true,
backgroundColorHex: ExcelColor.fromHexString('FFD9E1F2'),
horizontalAlign: HorizontalAlign.Center,
);
// 表头
final headers = ['code', 'name', 'temperature', 'airflowTime', 'status'];
sheet.appendRow(headers.map((h) => TextCellValue(h)).toList());
for (var i = 0; i < headers.length; i++) {
sheet
.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0))
.cellStyle = headerStyle;
}
// 示例行
final sample1 = ['P001', '示例程序-标准流程', 50, 60, 1];
final sample2 = ['P002', '示例程序-快速流程', 45, 30, 1];
sheet.appendRow(sample1.map((v) => TextCellValue(v.toString())).toList());
sheet.appendRow(sample2.map((v) => TextCellValue(v.toString())).toList());
// 说明行(用前缀标记,避免被解析)
final note = '说明code 必填且唯一name 必填temperature/airflowTime 整数status:1启用 0停用';
sheet.appendRow([TextCellValue(note)]);
// 列宽
sheet.setColumnWidth(0, 14);
sheet.setColumnWidth(1, 26);
sheet.setColumnWidth(2, 14);
sheet.setColumnWidth(3, 14);
sheet.setColumnWidth(4, 10);
}
void _writeStepsSheet(Excel excel) {
final sheet = excel[sheetSteps];
final headerStyle = CellStyle(
bold: true,
backgroundColorHex: ExcelColor.fromHexString('FFD9E1F2'),
horizontalAlign: HorizontalAlign.Center,
);
final headers = [
'program_code',
'step_no',
'position',
'name',
'mixTime',
'magnetTime',
'volume',
'blowTime',
'speed',
];
sheet.appendRow(headers.map((h) => TextCellValue(h)).toList());
for (var i = 0; i < headers.length; i++) {
sheet
.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0))
.cellStyle = headerStyle;
}
// 示例行P001 / P002 各两步
final samples = <List<Object>>[
['P001', 1, 'A1', '加样', 30, 0, 500, 0, 5],
['P001', 2, 'A2', '混合', 60, 10, 300, 30, 5],
['P002', 1, 'B1', '混合', 20, 5, 400, 0, 4],
];
for (final row in samples) {
sheet.appendRow(row.map((v) {
if (v is num) return IntCellValue(v.toInt());
return TextCellValue(v.toString());
}).toList());
}
// 说明行
final note =
'说明program_code 对应 Programs.codestep_no 整数从 1 开始position 形如 A1/B2mixTime/magnetTime/volume/blowTime 单位秒或微升speed 1-10';
sheet.appendRow([TextCellValue(note)]);
// 列宽
sheet.setColumnWidth(0, 14);
sheet.setColumnWidth(1, 10);
sheet.setColumnWidth(2, 12);
sheet.setColumnWidth(3, 14);
sheet.setColumnWidth(4, 10);
sheet.setColumnWidth(5, 12);
sheet.setColumnWidth(6, 10);
sheet.setColumnWidth(7, 10);
sheet.setColumnWidth(8, 8);
}
}