Files
nj/app/lib/data/repositories/remote_journal_repository.dart
iven 7e928ae1e1
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
fix(app): 修复 P2~P4 共 10 项前端问题
P2 必须修复:
- 教师布置主题 classId 从硬编码改为班级下拉选择器
- 班级日记墙使用服务端 classId 过滤替代前端过滤
- Profile 统计栏接入 JournalRepository 真实数据
- WeeklyPage 从全硬编码改为 JournalRepository 数据驱动

P3 建议改进:
- 提取 mood_utils.dart 公共函数,消除 4 处重复定义
- 贴纸库搜索框连接 StickerBloc 按名称过滤

P4 细节打磨:
- 家长页多孩子时显示 DropdownButton 选择器
- 搜索结果日记卡片点击跳转 /editor?id=
- MonthlyPage 照片数量从 JournalElement 统计
- calendar_page/mood_page/search_page 统一使用 moodToEmoji/moodToLabel
2026-06-02 20:21:51 +08:00

151 lines
4.8 KiB
Dart

// 远程日记仓库 — 通过 API 客户端连接后端
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;
RemoteJournalRepository({required ApiClient api}) : _api = api;
@override
Future<List<JournalEntry>> getJournals({
DateTime? dateFrom,
DateTime? dateTo,
int? page,
int? pageSize,
String? mood,
String? tag,
String? classId,
}) async {
final queryParams = <String, dynamic>{};
// 后端 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<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => JournalEntry.fromJson(json as Map<String, dynamic>))
.toList();
}
@override
Future<int> getJournalCount() async {
final response = await _api.get('/diary/journals', queryParams: {
'page': 1,
'page_size': 1,
});
final body = response.data as Map<String, dynamic>;
return (body['total'] as int?) ?? 0;
}
@override
Future<JournalEntry?> getJournal(String id) async {
try {
final response = await _api.get('/diary/journals/$id');
final body = response.data as Map<String, dynamic>;
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
} on ApiException catch (e) {
if (e.statusCode == 404) return null;
rethrow;
}
}
@override
Future<JournalEntry> createJournal(JournalEntry entry) async {
final response = await _api.post('/diary/journals', data: entry.toJson());
final body = response.data as Map<String, dynamic>;
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
}
@override
Future<JournalEntry> 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<String, dynamic>;
return JournalEntry.fromJson(body['data'] as Map<String, dynamic>);
}
@override
Future<void> deleteJournal(String id) async {
await _api.delete('/diary/journals/$id');
}
@override
Future<List<JournalElement>> getElements(String journalId) async {
final response = await _api.get('/diary/journals/$journalId/elements');
final body = response.data as Map<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => JournalElement.fromJson(json as Map<String, dynamic>))
.toList();
}
@override
Future<JournalElement> addElement(JournalElement element) async {
final response = await _api.post(
'/diary/journals/${element.journalId}/elements',
data: element.toJson(),
);
final body = response.data as Map<String, dynamic>;
return JournalElement.fromJson(body['data'] as Map<String, dynamic>);
}
@override
Future<JournalElement> 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<String, dynamic>;
return JournalElement.fromJson(body['data'] as Map<String, dynamic>);
}
@override
Future<void> removeElement(String elementId) async {
await _api.delete('/diary/elements/$elementId');
}
}
/// 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';
}