feat(app): pnpm 一键启动 + Flutter Web 编译修复
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

1. 新增 pnpm start:dev / pnpm start:stop 命令
   - scripts/dev.mjs: 跨平台启动脚本(后端+管理端+学生端)
   - scripts/stop.mjs: 端口清理停止脚本
   - 根 package.json 定义 pnpm 脚本

2. 修复 Flutter Web 编译(Isar 3.x + flutter_secure_storage 不兼容)
   - isar_database: 条件导出,Web 用空 stub
   - isar_journal_repository: 条件导出,Web 用空 stub
   - sync_engine: 条件导出,Web 用内存队列(无 Isar 持久化)
   - 移除 flutter_secure_storage(v9 web 插件用 dart:html)
   - 新增 SecureTokenStore 接口 + shared_preferences 实现
   - auth_repository 改用 SecureTokenStore 接口
This commit is contained in:
iven
2026-06-03 09:50:19 +08:00
parent b81a972245
commit 11d0971a67
23 changed files with 2034 additions and 888 deletions

View File

@@ -2,14 +2,14 @@
//
// 职责:
// - 封装后端认证 API 调用(登录/注册/刷新令牌/登出)
// - 通过 flutter_secure_storage 安全持久化 JWT 令牌PIPL 合规)
// - 通过 SecureTokenStore 安全持久化 JWT 令牌PIPL 合规)
// - 为 AuthBloc 提供干净的认证数据访问接口
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logger/logger.dart';
import '../local/secure_token_store.dart';
import '../models/auth_token.dart';
import '../models/user.dart';
import '../remote/api_client.dart';
@@ -33,11 +33,11 @@ class AuthException implements Exception {
/// 认证仓库 — 管理用户登录状态和令牌
///
/// 使用 [ApiClient] 与后端通信,使用 [FlutterSecureStorage] 持久化令牌。
/// 所有令牌操作都是加密存储,满足儿童数据 PIPL 合规要求
/// 使用 [ApiClient] 与后端通信,使用 [SecureTokenStore] 持久化令牌。
/// 原生平台使用加密存储Web 平台使用 shared_preferences
class AuthRepository {
final ApiClient _apiClient;
final FlutterSecureStorage _secureStorage;
final SecureTokenStore _tokenStore;
final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0));
AuthToken? _currentToken;
@@ -45,13 +45,9 @@ class AuthRepository {
AuthRepository({
required ApiClient apiClient,
FlutterSecureStorage? secureStorage,
required SecureTokenStore tokenStore,
}) : _apiClient = apiClient,
_secureStorage = secureStorage ??
const FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
);
_tokenStore = tokenStore;
/// 当前用户(可能为 null
User? get currentUser => _currentUser;
@@ -167,10 +163,10 @@ class AuthRepository {
_logger.d('恢复认证状态');
try {
final accessToken = await _secureStorage.read(key: _keyAccessToken);
final refreshTokenStr = await _secureStorage.read(key: _keyRefreshToken);
final expiresAtStr = await _secureStorage.read(key: _keyExpiresAt);
final userJsonStr = await _secureStorage.read(key: _keyUserJson);
final accessToken = await _tokenStore.read(_keyAccessToken);
final refreshTokenStr = await _tokenStore.read(_keyRefreshToken);
final expiresAtStr = await _tokenStore.read(_keyExpiresAt);
final userJsonStr = await _tokenStore.read(_keyUserJson);
if (accessToken == null || refreshTokenStr == null || userJsonStr == null) {
_logger.d('无存储的认证信息');
@@ -238,27 +234,27 @@ class AuthRepository {
_currentToken = token;
_currentUser = user;
await _saveToken(token);
await _secureStorage.write(
key: _keyUserJson,
value: jsonEncode(user.toJson()),
await _tokenStore.write(
_keyUserJson,
jsonEncode(user.toJson()),
);
}
/// 仅保存令牌到安全存储
Future<void> _saveToken(AuthToken token) async {
_currentToken = token;
await _secureStorage.write(key: _keyAccessToken, value: token.accessToken);
await _secureStorage.write(key: _keyRefreshToken, value: token.refreshToken);
await _secureStorage.write(key: _keyExpiresAt, value: token.expiresAt.toIso8601String());
await _tokenStore.write(_keyAccessToken, token.accessToken);
await _tokenStore.write(_keyRefreshToken, token.refreshToken);
await _tokenStore.write(_keyExpiresAt, token.expiresAt.toIso8601String());
}
/// 清除所有认证数据
Future<void> _clearAuth() async {
_currentToken = null;
_currentUser = null;
await _secureStorage.delete(key: _keyAccessToken);
await _secureStorage.delete(key: _keyRefreshToken);
await _secureStorage.delete(key: _keyExpiresAt);
await _secureStorage.delete(key: _keyUserJson);
await _tokenStore.delete(_keyAccessToken);
await _tokenStore.delete(_keyRefreshToken);
await _tokenStore.delete(_keyExpiresAt);
await _tokenStore.delete(_keyUserJson);
}
}

View File

@@ -1,362 +1,7 @@
// Isar 本地日记仓库 — 本地优先数据存储
// Isar 本地日记仓库 — 条件导出
//
// 实现 JournalRepository 抽象接口,所有数据存储在 Isar 本地数据库。
// 核心逻辑参考 InMemoryJournalRepository,替换内存 Map 为 Isar 查询。
//
// 转换层:
// - JournalEntry ↔ JournalEntryCollection通过 toCollection/fromCollection
// - JournalElement ↔ JournalElementCollection通过 toCollection/fromCollection
// 根据平台选择实现:
// - 原生平台 → isar_journal_repository_native.dartIsar 本地数据库)
// - Web 平台 → isar_journal_repository_web.dart空 stub应使用 RemoteJournalRepository
import 'dart:convert';
import 'package:isar/isar.dart';
import '../local/isar_database.dart';
import '../local/collections/journal_entry_collection.dart';
import '../local/collections/journal_element_collection.dart';
import '../models/journal_entry.dart';
import '../models/journal_element.dart';
import 'journal_repository.dart';
/// Isar 本地日记仓库 — JournalRepository 的 Isar 实现
class IsarJournalRepository implements JournalRepository {
Isar get _isar => IsarDatabase.instance!;
// ============================================================
// 日记 CRUD
// ============================================================
@override
Future<List<JournalEntry>> getJournals({
DateTime? dateFrom,
DateTime? dateTo,
int? page,
int? pageSize,
String? mood,
String? tag,
String? classId,
}) async {
var query = _isar.journalEntryCollections
.where()
.filter()
.isDeletedEqualTo(false);
// 日期范围过滤
if (dateFrom != null) {
query = query.and().dateEpochGreaterThan(dateFrom.millisecondsSinceEpoch);
}
if (dateTo != null) {
query = query.and().dateEpochLessThan(dateTo.millisecondsSinceEpoch);
}
// 心情过滤
if (mood != null) {
query = query.and().moodEqualTo(mood);
}
// 标签过滤Isar tagsJson 字段存储 JSON 数组,用 contains 匹配
if (tag != null) {
query = query.and().tagsJsonContains(tag);
}
// 班级过滤
if (classId != null) {
query = query.and().classIdEqualTo(classId);
}
// 按日期降序排列
var results = await query
.sortByDateEpochDesc()
.findAll();
// 分页
if (page != null && pageSize != null) {
final start = (page - 1) * pageSize;
if (start >= results.length) return [];
final end = (start + pageSize).clamp(0, results.length);
results = results.sublist(start, end);
}
return results.map(_fromCollection).toList();
}
@override
Future<int> getJournalCount() async {
return _isar.journalEntryCollections
.where()
.filter()
.isDeletedEqualTo(false)
.count();
}
@override
Future<JournalEntry?> getJournal(String id) async {
final col = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (col == null) return null;
return _fromCollection(col);
}
@override
Future<JournalEntry> createJournal(JournalEntry entry) async {
final col = _toEntryCollection(entry);
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(col);
});
return entry;
}
@override
Future<JournalEntry> updateJournal(JournalEntry entry) async {
final existing = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(entry.id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (existing == null) {
throw StateError('日记不存在: ${entry.id}');
}
// 乐观锁冲突检测
if (existing.version != entry.version) {
throw StateError(
'版本冲突: 本地版本 ${entry.version}, 存储版本 ${existing.version}',
);
}
final updated = entry.copyWith(
version: entry.version + 1,
updatedAt: DateTime.now(),
);
final col = _toEntryCollection(updated);
col.isarId = existing.isarId; // 保留 Isar 主键
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(col);
});
return updated;
}
@override
Future<void> deleteJournal(String id) async {
final existing = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(id)
.findFirst();
if (existing == null) return;
// 软删除日记
existing.isDeleted = true;
existing.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
// 软删除关联元素
final elements = await _isar.journalElementCollections
.where()
.filter()
.journalIdEqualTo(id)
.and()
.isDeletedEqualTo(false)
.findAll();
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(existing);
for (final el in elements) {
el.isDeleted = true;
el.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
await _isar.journalElementCollections.put(el);
}
});
}
// ============================================================
// 元素 CRUD
// ============================================================
@override
Future<List<JournalElement>> getElements(String journalId) async {
final results = await _isar.journalElementCollections
.where()
.filter()
.journalIdEqualTo(journalId)
.and()
.isDeletedEqualTo(false)
.sortByZIndex()
.findAll();
return results.map(_fromElementCollection).toList();
}
@override
Future<JournalElement> addElement(JournalElement element) async {
final col = _toElementCollection(element);
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(col);
});
return element;
}
@override
Future<JournalElement> updateElement(JournalElement element) async {
final existing = await _isar.journalElementCollections
.where()
.filter()
.idEqualTo(element.id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (existing == null) {
throw StateError('元素不存在: ${element.id}');
}
// 乐观锁冲突检测
if (existing.version != element.version) {
throw StateError(
'版本冲突: 本地版本 ${element.version}, 存储版本 ${existing.version}',
);
}
final updated = element.copyWith(
version: element.version + 1,
updatedAt: DateTime.now(),
);
final col = _toElementCollection(updated);
col.isarId = existing.isarId;
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(col);
});
return updated;
}
@override
Future<void> removeElement(String elementId) async {
final existing = await _isar.journalElementCollections
.where()
.filter()
.idEqualTo(elementId)
.findFirst();
if (existing == null) return;
// 软删除
existing.isDeleted = true;
existing.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(existing);
});
}
// ============================================================
// 转换函数JournalEntry ↔ JournalEntryCollection
// ============================================================
/// JournalEntry → JournalEntryCollection
JournalEntryCollection _toEntryCollection(JournalEntry entry) {
return JournalEntryCollection()
..id = entry.id
..authorId = entry.authorId
..classId = entry.classId
..title = entry.title
..dateEpoch = entry.date.millisecondsSinceEpoch
..mood = entry.mood.value
..weather = entry.weather.value
..tagsJson = jsonEncode(entry.tags)
..isPrivate = entry.isPrivate
..sharedToClass = entry.sharedToClass
..assignedTopicId = entry.assignedTopicId
..contentExcerpt = entry.contentExcerpt
..version = entry.version
..createdAtEpoch = entry.createdAt.millisecondsSinceEpoch
..updatedAtEpoch = entry.updatedAt.millisecondsSinceEpoch
..isDeleted = false;
}
/// JournalEntryCollection → JournalEntry
JournalEntry _fromCollection(JournalEntryCollection col) {
return JournalEntry(
id: col.id,
authorId: col.authorId,
classId: col.classId,
title: col.title,
date: DateTime.fromMillisecondsSinceEpoch(col.dateEpoch),
mood: Mood.values.firstWhere(
(m) => m.value == col.mood,
orElse: () => Mood.calm,
),
weather: Weather.values.firstWhere(
(w) => w.value == col.weather,
orElse: () => Weather.sunny,
),
tags: List<String>.from(
jsonDecode(col.tagsJson) as List? ?? [],
),
isPrivate: col.isPrivate,
sharedToClass: col.sharedToClass,
assignedTopicId: col.assignedTopicId,
contentExcerpt: col.contentExcerpt,
version: col.version,
createdAt: DateTime.fromMillisecondsSinceEpoch(col.createdAtEpoch),
updatedAt: DateTime.fromMillisecondsSinceEpoch(col.updatedAtEpoch),
);
}
// ============================================================
// 转换函数JournalElement ↔ JournalElementCollection
// ============================================================
/// JournalElement → JournalElementCollection
JournalElementCollection _toElementCollection(JournalElement element) {
return JournalElementCollection()
..id = element.id
..journalId = element.journalId
..elementType = element.elementType.value
..positionX = element.positionX
..positionY = element.positionY
..width = element.width
..height = element.height
..rotation = element.rotation
..zIndex = element.zIndex
..contentJson = jsonEncode(element.content)
..version = element.version
..createdAtEpoch = element.createdAt.millisecondsSinceEpoch
..updatedAtEpoch = element.updatedAt.millisecondsSinceEpoch
..isDeleted = false;
}
/// JournalElementCollection → JournalElement
JournalElement _fromElementCollection(JournalElementCollection col) {
return JournalElement(
id: col.id,
journalId: col.journalId,
elementType: ElementType.values.firstWhere(
(e) => e.value == col.elementType,
orElse: () => ElementType.text,
),
positionX: col.positionX,
positionY: col.positionY,
width: col.width,
height: col.height,
rotation: col.rotation,
zIndex: col.zIndex,
content: Map<String, dynamic>.from(
jsonDecode(col.contentJson) as Map? ?? {},
),
version: col.version,
createdAt: DateTime.fromMillisecondsSinceEpoch(col.createdAtEpoch),
updatedAt: DateTime.fromMillisecondsSinceEpoch(col.updatedAtEpoch),
);
}
}
export 'isar_journal_repository_web.dart' if (dart.library.io) 'isar_journal_repository_native.dart';

View File

@@ -0,0 +1,362 @@
// Isar 本地日记仓库 — 本地优先数据存储
//
// 实现 JournalRepository 抽象接口,所有数据存储在 Isar 本地数据库。
// 核心逻辑参考 InMemoryJournalRepository替换内存 Map 为 Isar 查询。
//
// 转换层:
// - JournalEntry ↔ JournalEntryCollection通过 toCollection/fromCollection
// - JournalElement ↔ JournalElementCollection通过 toCollection/fromCollection
import 'dart:convert';
import 'package:isar/isar.dart';
import '../local/isar_database.dart';
import '../local/collections/journal_entry_collection.dart';
import '../local/collections/journal_element_collection.dart';
import '../models/journal_entry.dart';
import '../models/journal_element.dart';
import 'journal_repository.dart';
/// Isar 本地日记仓库 — JournalRepository 的 Isar 实现
class IsarJournalRepository implements JournalRepository {
Isar get _isar => IsarDatabase.instance!;
// ============================================================
// 日记 CRUD
// ============================================================
@override
Future<List<JournalEntry>> getJournals({
DateTime? dateFrom,
DateTime? dateTo,
int? page,
int? pageSize,
String? mood,
String? tag,
String? classId,
}) async {
var query = _isar.journalEntryCollections
.where()
.filter()
.isDeletedEqualTo(false);
// 日期范围过滤
if (dateFrom != null) {
query = query.and().dateEpochGreaterThan(dateFrom.millisecondsSinceEpoch);
}
if (dateTo != null) {
query = query.and().dateEpochLessThan(dateTo.millisecondsSinceEpoch);
}
// 心情过滤
if (mood != null) {
query = query.and().moodEqualTo(mood);
}
// 标签过滤Isar tagsJson 字段存储 JSON 数组,用 contains 匹配
if (tag != null) {
query = query.and().tagsJsonContains(tag);
}
// 班级过滤
if (classId != null) {
query = query.and().classIdEqualTo(classId);
}
// 按日期降序排列
var results = await query
.sortByDateEpochDesc()
.findAll();
// 分页
if (page != null && pageSize != null) {
final start = (page - 1) * pageSize;
if (start >= results.length) return [];
final end = (start + pageSize).clamp(0, results.length);
results = results.sublist(start, end);
}
return results.map(_fromCollection).toList();
}
@override
Future<int> getJournalCount() async {
return _isar.journalEntryCollections
.where()
.filter()
.isDeletedEqualTo(false)
.count();
}
@override
Future<JournalEntry?> getJournal(String id) async {
final col = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (col == null) return null;
return _fromCollection(col);
}
@override
Future<JournalEntry> createJournal(JournalEntry entry) async {
final col = _toEntryCollection(entry);
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(col);
});
return entry;
}
@override
Future<JournalEntry> updateJournal(JournalEntry entry) async {
final existing = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(entry.id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (existing == null) {
throw StateError('日记不存在: ${entry.id}');
}
// 乐观锁冲突检测
if (existing.version != entry.version) {
throw StateError(
'版本冲突: 本地版本 ${entry.version}, 存储版本 ${existing.version}',
);
}
final updated = entry.copyWith(
version: entry.version + 1,
updatedAt: DateTime.now(),
);
final col = _toEntryCollection(updated);
col.isarId = existing.isarId; // 保留 Isar 主键
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(col);
});
return updated;
}
@override
Future<void> deleteJournal(String id) async {
final existing = await _isar.journalEntryCollections
.where()
.filter()
.idEqualTo(id)
.findFirst();
if (existing == null) return;
// 软删除日记
existing.isDeleted = true;
existing.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
// 软删除关联元素
final elements = await _isar.journalElementCollections
.where()
.filter()
.journalIdEqualTo(id)
.and()
.isDeletedEqualTo(false)
.findAll();
await _isar.writeTxn(() async {
await _isar.journalEntryCollections.put(existing);
for (final el in elements) {
el.isDeleted = true;
el.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
await _isar.journalElementCollections.put(el);
}
});
}
// ============================================================
// 元素 CRUD
// ============================================================
@override
Future<List<JournalElement>> getElements(String journalId) async {
final results = await _isar.journalElementCollections
.where()
.filter()
.journalIdEqualTo(journalId)
.and()
.isDeletedEqualTo(false)
.sortByZIndex()
.findAll();
return results.map(_fromElementCollection).toList();
}
@override
Future<JournalElement> addElement(JournalElement element) async {
final col = _toElementCollection(element);
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(col);
});
return element;
}
@override
Future<JournalElement> updateElement(JournalElement element) async {
final existing = await _isar.journalElementCollections
.where()
.filter()
.idEqualTo(element.id)
.and()
.isDeletedEqualTo(false)
.findFirst();
if (existing == null) {
throw StateError('元素不存在: ${element.id}');
}
// 乐观锁冲突检测
if (existing.version != element.version) {
throw StateError(
'版本冲突: 本地版本 ${element.version}, 存储版本 ${existing.version}',
);
}
final updated = element.copyWith(
version: element.version + 1,
updatedAt: DateTime.now(),
);
final col = _toElementCollection(updated);
col.isarId = existing.isarId;
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(col);
});
return updated;
}
@override
Future<void> removeElement(String elementId) async {
final existing = await _isar.journalElementCollections
.where()
.filter()
.idEqualTo(elementId)
.findFirst();
if (existing == null) return;
// 软删除
existing.isDeleted = true;
existing.updatedAtEpoch = DateTime.now().millisecondsSinceEpoch;
await _isar.writeTxn(() async {
await _isar.journalElementCollections.put(existing);
});
}
// ============================================================
// 转换函数JournalEntry ↔ JournalEntryCollection
// ============================================================
/// JournalEntry → JournalEntryCollection
JournalEntryCollection _toEntryCollection(JournalEntry entry) {
return JournalEntryCollection()
..id = entry.id
..authorId = entry.authorId
..classId = entry.classId
..title = entry.title
..dateEpoch = entry.date.millisecondsSinceEpoch
..mood = entry.mood.value
..weather = entry.weather.value
..tagsJson = jsonEncode(entry.tags)
..isPrivate = entry.isPrivate
..sharedToClass = entry.sharedToClass
..assignedTopicId = entry.assignedTopicId
..contentExcerpt = entry.contentExcerpt
..version = entry.version
..createdAtEpoch = entry.createdAt.millisecondsSinceEpoch
..updatedAtEpoch = entry.updatedAt.millisecondsSinceEpoch
..isDeleted = false;
}
/// JournalEntryCollection → JournalEntry
JournalEntry _fromCollection(JournalEntryCollection col) {
return JournalEntry(
id: col.id,
authorId: col.authorId,
classId: col.classId,
title: col.title,
date: DateTime.fromMillisecondsSinceEpoch(col.dateEpoch),
mood: Mood.values.firstWhere(
(m) => m.value == col.mood,
orElse: () => Mood.calm,
),
weather: Weather.values.firstWhere(
(w) => w.value == col.weather,
orElse: () => Weather.sunny,
),
tags: List<String>.from(
jsonDecode(col.tagsJson) as List? ?? [],
),
isPrivate: col.isPrivate,
sharedToClass: col.sharedToClass,
assignedTopicId: col.assignedTopicId,
contentExcerpt: col.contentExcerpt,
version: col.version,
createdAt: DateTime.fromMillisecondsSinceEpoch(col.createdAtEpoch),
updatedAt: DateTime.fromMillisecondsSinceEpoch(col.updatedAtEpoch),
);
}
// ============================================================
// 转换函数JournalElement ↔ JournalElementCollection
// ============================================================
/// JournalElement → JournalElementCollection
JournalElementCollection _toElementCollection(JournalElement element) {
return JournalElementCollection()
..id = element.id
..journalId = element.journalId
..elementType = element.elementType.value
..positionX = element.positionX
..positionY = element.positionY
..width = element.width
..height = element.height
..rotation = element.rotation
..zIndex = element.zIndex
..contentJson = jsonEncode(element.content)
..version = element.version
..createdAtEpoch = element.createdAt.millisecondsSinceEpoch
..updatedAtEpoch = element.updatedAt.millisecondsSinceEpoch
..isDeleted = false;
}
/// JournalElementCollection → JournalElement
JournalElement _fromElementCollection(JournalElementCollection col) {
return JournalElement(
id: col.id,
journalId: col.journalId,
elementType: ElementType.values.firstWhere(
(e) => e.value == col.elementType,
orElse: () => ElementType.text,
),
positionX: col.positionX,
positionY: col.positionY,
width: col.width,
height: col.height,
rotation: col.rotation,
zIndex: col.zIndex,
content: Map<String, dynamic>.from(
jsonDecode(col.contentJson) as Map? ?? {},
),
version: col.version,
createdAt: DateTime.fromMillisecondsSinceEpoch(col.createdAtEpoch),
updatedAt: DateTime.fromMillisecondsSinceEpoch(col.updatedAtEpoch),
);
}
}

View File

@@ -0,0 +1,59 @@
// Isar 本地日记仓库 — Web 平台 stub不可用
//
// Isar 3.x 不支持 Web此文件提供空实现。
// Web 平台应使用 RemoteJournalRepository。
import '../models/journal_entry.dart';
import '../models/journal_element.dart';
import 'journal_repository.dart';
/// Isar 本地日记仓库 — Web 空实现(抛出 UnsupportedError
class IsarJournalRepository implements JournalRepository {
@override
Future<List<JournalEntry>> getJournals({
DateTime? dateFrom,
DateTime? dateTo,
int? page,
int? pageSize,
String? mood,
String? tag,
String? classId,
}) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<int> getJournalCount() =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<JournalEntry?> getJournal(String id) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<JournalEntry> createJournal(JournalEntry entry) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<JournalEntry> updateJournal(JournalEntry entry) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<void> deleteJournal(String id) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<List<JournalElement>> getElements(String journalId) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<JournalElement> addElement(JournalElement element) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<JournalElement> updateElement(JournalElement element) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
@override
Future<void> removeElement(String elementId) =>
throw UnsupportedError('IsarJournalRepository 不支持 Web 平台');
}