// 远程日记仓库 — 通过 API 客户端连接后端 import 'dart:async'; import '../models/journal_element.dart'; import '../models/journal_entry.dart'; import '../remote/api_client.dart'; import 'journal_repository.dart'; /// 远程日记仓库 — 通过 HTTP API 操作后端数据 /// /// 所有操作需要网络连接。离线场景由 SyncEngine 协调 Isar 本地仓库处理。 class RemoteJournalRepository implements JournalRepository { final ApiClient _api; /// 变更通知流控制器 — 写操作成功后通知 UI 刷新 final StreamController _changeController = StreamController.broadcast(); RemoteJournalRepository({required ApiClient api}) : _api = api; @override Future> getJournals({ DateTime? dateFrom, DateTime? dateTo, int? page, int? pageSize, String? mood, String? tag, String? classId, }) async { final queryParams = {}; // 后端 NaiveDateTime 格式: "2026-06-01T00:00:00"(不带毫秒) if (dateFrom != null) { queryParams['date_from'] = dateFrom.toIso8601String().replaceFirst(RegExp(r'\.\d+'), ''); } if (dateTo != null) { queryParams['date_to'] = dateTo.toIso8601String().replaceFirst(RegExp(r'\.\d+'), ''); } if (page != null) queryParams['page'] = page; if (pageSize != null) queryParams['page_size'] = pageSize; if (mood != null) queryParams['mood'] = mood; if (tag != null) queryParams['tag'] = tag; if (classId != null) queryParams['class_id'] = classId; final response = await _api.get('/diary/journals', queryParams: queryParams); final body = response.data as Map; // 后端信封格式: { success, data: { data: [...], total, page, ... }, message } final envelope = body['data'] as Map? ?? {}; final items = envelope['data'] as List? ?? []; return items .map((json) => JournalEntry.fromJson(json as Map)) .toList(); } @override Future getJournalCount() async { final response = await _api.get('/diary/journals', queryParams: { 'page': 1, 'page_size': 1, }); final body = response.data as Map; // 后端信封格式: { success, data: { data: [...], total, ... }, message } final envelope = body['data'] as Map? ?? {}; return (envelope['total'] as int?) ?? 0; } @override Future getJournal(String id) async { try { final response = await _api.get('/diary/journals/$id'); final body = response.data as Map; return JournalEntry.fromJson(body['data'] as Map); } on ApiException catch (e) { if (e.statusCode == 404) return null; rethrow; } } @override Future createJournal(JournalEntry entry) async { // 后端 CreateJournalReq.date 是 NaiveDate(只有日期),需转换格式 final json = entry.toJson(); json['date'] = entry.date.toIso8601String().substring(0, 10); final response = await _api.post('/diary/journals', data: json); final body = response.data as Map; final created = JournalEntry.fromJson(body['data'] as Map); _changeController.add(null); // 通知 UI 刷新列表 return created; } @override Future updateJournal(JournalEntry entry) async { final response = await _api.put( '/diary/journals/${entry.id}', data: { 'title': entry.title, 'mood': entry.mood.value, 'weather': entry.weather.value, 'tags': entry.tags, 'is_private': entry.isPrivate, 'shared_to_class': entry.sharedToClass, 'version': entry.version, }, ); final body = response.data as Map; _changeController.add(null); // 通知 UI 刷新列表 return JournalEntry.fromJson(body['data'] as Map); } @override Future deleteJournal(String id) async { await _api.delete('/diary/journals/$id'); _changeController.add(null); // 通知 UI 刷新列表 } @override Future> getElements(String journalId) async { final response = await _api.get('/diary/journals/$journalId/elements'); final body = response.data as Map; final items = body['data'] as List? ?? []; return items .map((json) => JournalElement.fromJson(json as Map)) .toList(); } @override Future addElement(JournalElement element) async { final response = await _api.post( '/diary/journals/${element.journalId}/elements', data: element.toJson(), ); final body = response.data as Map; return JournalElement.fromJson(body['data'] as Map); } @override Future updateElement(JournalElement element) async { final response = await _api.put( '/diary/journals/${element.journalId}/elements/${element.id}', data: element.toJson(), ); final body = response.data as Map; return JournalElement.fromJson(body['data'] as Map); } @override Future removeElement(String elementId) async { await _api.delete('/diary/elements/$elementId'); } /// 变更通知流 — 写操作后广播,供 HomeBloc 自动刷新 @override Stream get onJournalChanged => _changeController.stream; } /// API 异常封装 — 后端返回非 2xx 状态码时抛出 class ApiException implements Exception { final String message; final int statusCode; final dynamic responseBody; const ApiException({ required this.message, required this.statusCode, this.responseBody, }); @override String toString() => 'ApiException($statusCode): $message'; }