Files
jizhi/frontend/lib/shared/services/local_export_service.dart
T
2026-07-24 23:11:20 +08:00

131 lines
3.7 KiB
Dart

import 'dart:convert';
import 'package:archive/archive.dart';
import 'package:miaoji_zhang/shared/services/local_database.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
class LocalExportService {
const LocalExportService._();
static List<int> buildZip() {
final snapshot = LocalDatabase.instance.exportSnapshot();
final session = SessionStore.instance;
return buildZipFromSnapshot(
snapshot,
profile: {
'mode': session.isGuest ? 'guest' : 'account',
'userId': session.userId,
'username': session.username,
'nickname': session.nickname,
},
);
}
static List<int> buildZipFromSnapshot(
Map<String, dynamic> snapshot, {
Map<String, dynamic> profile = const {},
}) {
final document = <String, dynamic>{...snapshot, 'profile': profile};
final ledgers = _indexById(snapshot['ledgers'] as List<dynamic>);
final categories = _indexById(snapshot['categories'] as List<dynamic>);
final transactions = (snapshot['transactions'] as List<dynamic>)
.cast<Map<String, dynamic>>();
final budgets = (snapshot['budgets'] as List<dynamic>)
.cast<Map<String, dynamic>>();
final archive = Archive();
archive.addFile(
ArchiveFile.bytes(
'transactions.csv',
_utf8Bom(
_csv([
const [
'id',
'ledger',
'type',
'amount',
'category',
'note',
'payment_method',
'source',
'occurred_at',
'is_deleted',
'updated_at',
],
for (final item in transactions)
[
item['id'],
ledgers[item['ledgerId']]?['name'],
item['type'],
(item['amount'] as num).toStringAsFixed(2),
item['categoryName'],
item['note'],
item['paymentMethod'],
item['source'],
item['occurredAt'],
item['isDeleted'],
item['updatedAt'],
],
]),
),
),
);
archive.addFile(
ArchiveFile.bytes(
'budgets.csv',
_utf8Bom(
_csv([
const [
'ledger',
'period',
'category',
'amount',
'recurring',
'updated_at',
],
for (final item in budgets)
[
ledgers[item['ledgerId']]?['name'],
item['period'],
item['categoryId'] == null
? 'total'
: categories[item['categoryId']]?['name'],
(item['amount'] as num).toStringAsFixed(2),
item['recurring'],
item['updatedAt'],
],
]),
),
),
);
archive.addFile(
ArchiveFile.string(
'jizhi-backup.json',
const JsonEncoder.withIndent(' ').convert(document),
),
);
return ZipEncoder().encode(archive);
}
static Map<int, Map<String, dynamic>> _indexById(List<dynamic> items) => {
for (final item in items.cast<Map<String, dynamic>>())
item['id'] as int: item,
};
static List<int> _utf8Bom(String value) => [
0xef,
0xbb,
0xbf,
...utf8.encode(value),
];
static String _csv(List<List<Object?>> rows) =>
rows.map((row) => row.map(_csvCell).join(',')).join('\r\n');
static String _csvCell(Object? value) {
final text = value?.toString() ?? '';
if (!text.contains(RegExp('[,"\r\n]'))) return text;
return '"${text.replaceAll('"', '""')}"';
}
}