|
|
|
@ -6,11 +6,55 @@ import 'package:win_text_editor/app/providers/logger.dart';
@@ -6,11 +6,55 @@ import 'package:win_text_editor/app/providers/logger.dart';
|
|
|
|
|
|
|
|
|
|
class ExcelDealService { |
|
|
|
|
static const String _logTag = 'ExcelDealService'; |
|
|
|
|
static const Map<int, String> _columnTitles = { |
|
|
|
|
4: '日', |
|
|
|
|
5: '月', |
|
|
|
|
6: '水', |
|
|
|
|
7: '金', |
|
|
|
|
8: '火', |
|
|
|
|
9: '木', |
|
|
|
|
10: '土', |
|
|
|
|
11: '天', |
|
|
|
|
12: '海', |
|
|
|
|
13: '冥', |
|
|
|
|
14: '南', |
|
|
|
|
15: '北', |
|
|
|
|
16: '孛', |
|
|
|
|
17: '凯', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static const List<Set<String>> harmonyGroups = [ |
|
|
|
|
{'寅', '午', '戌'}, // 寅午戌合 |
|
|
|
|
{'亥', '卯', '未'}, // 亥卯未合 |
|
|
|
|
{'申', '子', '辰'}, // 申子辰合 |
|
|
|
|
{'巳', '酉', '丑'}, // 巳酉丑合 |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
static const List<Set<String>> conflictGroups = [ |
|
|
|
|
{'子', '午'}, // 子午冲 |
|
|
|
|
{'丑', '未'}, // 丑未冲 |
|
|
|
|
{'寅', '申'}, // 寅申冲 |
|
|
|
|
{'卯', '酉'}, // 卯酉冲 |
|
|
|
|
{'辰', '戌'}, // 辰戌冲 |
|
|
|
|
{'巳', '亥'}, // 巳亥冲 |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
static const List<Set<String>> punishmentGroups = [ |
|
|
|
|
{'子', '卯'}, // 子卯刑 |
|
|
|
|
{'子', '酉'}, // 子酉刑 |
|
|
|
|
{'午', '酉'}, // 午酉刑 |
|
|
|
|
{'午', '卯'}, // 午卯刑 |
|
|
|
|
{'寅', '亥'}, // 寅亥刑 |
|
|
|
|
{'巳', '申'}, // 巳申刑 |
|
|
|
|
{'丑', '辰'}, // 丑辰刑 |
|
|
|
|
{'寅', '巳', '申'}, // 寅巳申刑 |
|
|
|
|
{'未', '戌', '丑'}, // 未戌丑刑 |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
Future<void> exportToExcel(String processedContent, {String? fileName}) async { |
|
|
|
|
try { |
|
|
|
|
final excel = Excel.createExcel(); |
|
|
|
|
final sheet = excel['Sheet1']!; |
|
|
|
|
final sheet = excel['Sheet1']; |
|
|
|
|
|
|
|
|
|
final lines = processedContent.split('\n'); |
|
|
|
|
bool isHeaderProcessed = false; |
|
|
|
@ -51,27 +95,20 @@ class ExcelDealService {
@@ -51,27 +95,20 @@ class ExcelDealService {
|
|
|
|
|
final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: rowIndex)); |
|
|
|
|
cell.value = TextCellValue(cellValue); |
|
|
|
|
|
|
|
|
|
// 如果是标题行之后的数据行,添加三列空数据 |
|
|
|
|
if (isHeaderProcessed && col == cells.length - 1) { |
|
|
|
|
for (int i = 0; i < 3; i++) { |
|
|
|
|
sheet |
|
|
|
|
.cell( |
|
|
|
|
CellIndex.indexByColumnRow(columnIndex: cells.length + i, rowIndex: rowIndex), |
|
|
|
|
) |
|
|
|
|
.value = TextCellValue(''); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 应用红色格式到7-14列(索引6-13) |
|
|
|
|
if (col >= 6 && col <= 13) { |
|
|
|
|
_applyConditionalFormatting(cell, cellValue); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 如果是数据行且已处理过标题行,计算关系 |
|
|
|
|
if (isHeaderProcessed && rowIndex > 0) { |
|
|
|
|
_calculateRelationships(sheet, rowIndex, cells); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 设置列宽 |
|
|
|
|
for (var i = 1; i < 18; i++) { |
|
|
|
|
// 增加列宽设置范围以包含新列 |
|
|
|
|
sheet.setColumnWidth(i, 8.0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -98,20 +135,179 @@ class ExcelDealService {
@@ -98,20 +135,179 @@ class ExcelDealService {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void _calculateRelationships(Sheet sheet, int rowIndex, List<String> cells) { |
|
|
|
|
final List<CellData> validCells = []; |
|
|
|
|
|
|
|
|
|
// 1. Extract valid cells (columns 5-18, excluding 15-16) with degree, zodiac and minute |
|
|
|
|
for (int col = 4; col <= 17; col++) { |
|
|
|
|
if (col == 14 || col == 15) continue; // Skip South/North columns |
|
|
|
|
|
|
|
|
|
if (col < cells.length) { |
|
|
|
|
final data = _parseCellData(cells[col]); |
|
|
|
|
if (data != null && data.isValid()) { |
|
|
|
|
validCells.add(data..columnIndex = col); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final List<String> heRelations = []; // 合关系 |
|
|
|
|
final List<String> chongRelations = []; // 冲关系 |
|
|
|
|
final List<String> xingRelations = []; // 刑关系 |
|
|
|
|
|
|
|
|
|
// 2. Compare all valid cells pairwise |
|
|
|
|
for (int i = 0; i < validCells.length; i++) { |
|
|
|
|
for (int j = i + 1; j < validCells.length; j++) { |
|
|
|
|
final cell1 = validCells[i]; |
|
|
|
|
final cell2 = validCells[j]; |
|
|
|
|
|
|
|
|
|
// Get column titles |
|
|
|
|
final title1 = _columnTitles[cell1.columnIndex] ?? ''; |
|
|
|
|
final title2 = _columnTitles[cell2.columnIndex] ?? ''; |
|
|
|
|
|
|
|
|
|
// Check for harmony (合) |
|
|
|
|
if (_isHarmony(cell1, cell2)) { |
|
|
|
|
heRelations.add('$title1$title2合'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check for conflict (冲) |
|
|
|
|
if (_isConflict(cell1.zodiac, cell2.zodiac)) { |
|
|
|
|
chongRelations.add('$title1$title2冲'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check for punishment (刑) |
|
|
|
|
if (_isPunishment(cell1.zodiac, cell2.zodiac)) { |
|
|
|
|
xingRelations.add('$title1$title2刑'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 3. 写入关系列 (第19-21列) |
|
|
|
|
if (heRelations.isNotEmpty) { |
|
|
|
|
sheet |
|
|
|
|
.cell(CellIndex.indexByColumnRow(columnIndex: cells.length, rowIndex: rowIndex)) |
|
|
|
|
.value = TextCellValue(heRelations.join(' ')); |
|
|
|
|
} |
|
|
|
|
if (chongRelations.isNotEmpty) { |
|
|
|
|
sheet |
|
|
|
|
.cell(CellIndex.indexByColumnRow(columnIndex: cells.length + 1, rowIndex: rowIndex)) |
|
|
|
|
.value = TextCellValue(chongRelations.join(' ')); |
|
|
|
|
} |
|
|
|
|
if (xingRelations.isNotEmpty) { |
|
|
|
|
sheet |
|
|
|
|
.cell(CellIndex.indexByColumnRow(columnIndex: cells.length + 2, rowIndex: rowIndex)) |
|
|
|
|
.value = TextCellValue(xingRelations.join(' ')); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
CellData? _parseCellData(String cellValue) { |
|
|
|
|
// 支持两种格式: |
|
|
|
|
// 1. 度地支分(如"12酉34") |
|
|
|
|
// 2. 度地支分'秒(如"10丑48'49"或"11°50'00") |
|
|
|
|
|
|
|
|
|
final match = RegExp( |
|
|
|
|
r"^(\d+)([^\d\s°\']+)(\d+)(?:[\'](\d{1,2}))?$", |
|
|
|
|
).firstMatch(cellValue.trim()); |
|
|
|
|
if (match == null) return null; |
|
|
|
|
|
|
|
|
|
final degreeStr = match.group(1); |
|
|
|
|
final zodiac = match.group(2); |
|
|
|
|
final minuteStr = match.group(3); |
|
|
|
|
final secondStr = match.group(4); // 可能为null |
|
|
|
|
|
|
|
|
|
if (degreeStr == null || zodiac == null || minuteStr == null) return null; |
|
|
|
|
|
|
|
|
|
// 转换度分秒为double类型的度 |
|
|
|
|
final degree = double.tryParse(degreeStr); |
|
|
|
|
final minute = double.tryParse(minuteStr); |
|
|
|
|
final second = secondStr != null ? double.tryParse(secondStr) : 0.0; |
|
|
|
|
|
|
|
|
|
if (degree == null || minute == null) return null; |
|
|
|
|
|
|
|
|
|
// 将秒转换为分钟的小数部分(60秒=1分钟) |
|
|
|
|
final totalMinutes = minute + (second ?? 0) / 60; |
|
|
|
|
|
|
|
|
|
return CellData(degree: degree, zodiac: zodiac, minute: totalMinutes); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool _isHarmony(CellData cell1, CellData cell2) { |
|
|
|
|
// 排除相同地支 |
|
|
|
|
if (cell1.zodiac == cell2.zodiac) return false; |
|
|
|
|
|
|
|
|
|
// 1. 先检查度数差是否在1度以内 |
|
|
|
|
final totalDiff = (cell1.totalMinutes - cell2.totalMinutes).abs(); |
|
|
|
|
final isDegreeMatch = totalDiff <= 60; |
|
|
|
|
if (isDegreeMatch) { |
|
|
|
|
// 如果度数差在1度以内,直接返回true |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 2. 检查地支是否在合局中r |
|
|
|
|
var isZodiacMatch = false; |
|
|
|
|
for (final group in harmonyGroups) { |
|
|
|
|
if (group.contains(cell1.zodiac) && group.contains(cell2.zodiac)) { |
|
|
|
|
isZodiacMatch = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 两种条件满足其一即可 |
|
|
|
|
return isDegreeMatch || isZodiacMatch; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool _isConflict(String zodiac1, String zodiac2) { |
|
|
|
|
// 排除相同地支 |
|
|
|
|
if (zodiac1 == zodiac2) return false; |
|
|
|
|
|
|
|
|
|
for (final group in conflictGroups) { |
|
|
|
|
if (group.contains(zodiac1) && group.contains(zodiac2)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool _isPunishment(String zodiac1, String zodiac2) { |
|
|
|
|
// 排除相同地支 |
|
|
|
|
if (zodiac1 == zodiac2) return false; |
|
|
|
|
|
|
|
|
|
for (final group in punishmentGroups) { |
|
|
|
|
if (group.contains(zodiac1) && group.contains(zodiac2)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 跟踪每列的红色区域状态 |
|
|
|
|
final List<bool> _columnRedStates = List.filled(14, false); |
|
|
|
|
|
|
|
|
|
void _applyConditionalFormatting(Data cell, String value) { |
|
|
|
|
// 根据标记更新状态 |
|
|
|
|
if (value.contains('R')) { |
|
|
|
|
_columnRedStates[cell.cellIndex.columnIndex] = true; |
|
|
|
|
} else if (value.contains('D')) { |
|
|
|
|
_columnRedStates[cell.cellIndex.columnIndex] = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 在红色区域应用红色 |
|
|
|
|
if (_columnRedStates[cell.cellIndex.columnIndex]) { |
|
|
|
|
cell.cellStyle = CellStyle(fontColorHex: ExcelColor.red); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class CellData { |
|
|
|
|
final double? degree; |
|
|
|
|
final String zodiac; |
|
|
|
|
final double? minute; |
|
|
|
|
int columnIndex = -1; |
|
|
|
|
|
|
|
|
|
// 定义有效的地支集合 |
|
|
|
|
static const validZodiacs = {'亥', '戌', '酉', '申', '未', '午', '巳', '辰', '卯', '寅', '丑', '子'}; |
|
|
|
|
|
|
|
|
|
CellData({this.degree, required this.zodiac, this.minute}); |
|
|
|
|
|
|
|
|
|
bool isValid() { |
|
|
|
|
return degree != null && minute != null && validZodiacs.contains(zodiac); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
double get totalMinutes => (degree ?? 0) * 60 + (minute ?? 0); |
|
|
|
|
} |
|
|
|
|