485 lines
15 KiB
Dart
485 lines
15 KiB
Dart
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
|
import '../models/word_model.dart';
|
|||
|
|
import '../models/vocabulary_book_model.dart';
|
|||
|
|
import '../models/study_session_model.dart';
|
|||
|
|
import '../models/daily_stats_model.dart';
|
|||
|
|
import '../services/vocabulary_service.dart';
|
|||
|
|
import '../../../core/network/api_client.dart';
|
|||
|
|
import '../../../core/services/storage_service.dart';
|
|||
|
|
import '../services/learning_stats_service.dart';
|
|||
|
|
import '../models/learning_stats_model.dart';
|
|||
|
|
|
|||
|
|
/// 词汇状态
|
|||
|
|
class VocabularyState {
|
|||
|
|
final bool isLoading;
|
|||
|
|
final String? error;
|
|||
|
|
final List<VocabularyBook> systemBooks;
|
|||
|
|
final List<VocabularyBook> userBooks;
|
|||
|
|
final List<Map<String, dynamic>> categories;
|
|||
|
|
final List<Word> todayWords;
|
|||
|
|
final List<Word> reviewWords;
|
|||
|
|
final StudyStatistics? todayStatistics;
|
|||
|
|
final StudySession? currentSession;
|
|||
|
|
final DailyStats? dailyStats;
|
|||
|
|
final Map<String, dynamic>? overallStats;
|
|||
|
|
final int weeklyWordsStudied;
|
|||
|
|
|
|||
|
|
const VocabularyState({
|
|||
|
|
this.isLoading = false,
|
|||
|
|
this.error,
|
|||
|
|
this.systemBooks = const [],
|
|||
|
|
this.userBooks = const [],
|
|||
|
|
this.categories = const [],
|
|||
|
|
this.todayWords = const [],
|
|||
|
|
this.reviewWords = const [],
|
|||
|
|
this.todayStatistics,
|
|||
|
|
this.currentSession,
|
|||
|
|
this.dailyStats,
|
|||
|
|
this.overallStats,
|
|||
|
|
this.weeklyWordsStudied = 0,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
VocabularyState copyWith({
|
|||
|
|
bool? isLoading,
|
|||
|
|
String? error,
|
|||
|
|
List<VocabularyBook>? systemBooks,
|
|||
|
|
List<VocabularyBook>? userBooks,
|
|||
|
|
List<Map<String, dynamic>>? categories,
|
|||
|
|
List<Word>? todayWords,
|
|||
|
|
List<Word>? reviewWords,
|
|||
|
|
StudyStatistics? todayStatistics,
|
|||
|
|
StudySession? currentSession,
|
|||
|
|
DailyStats? dailyStats,
|
|||
|
|
Map<String, dynamic>? overallStats,
|
|||
|
|
int? weeklyWordsStudied,
|
|||
|
|
}) {
|
|||
|
|
return VocabularyState(
|
|||
|
|
isLoading: isLoading ?? this.isLoading,
|
|||
|
|
error: error,
|
|||
|
|
systemBooks: systemBooks ?? this.systemBooks,
|
|||
|
|
userBooks: userBooks ?? this.userBooks,
|
|||
|
|
categories: categories ?? this.categories,
|
|||
|
|
todayWords: todayWords ?? this.todayWords,
|
|||
|
|
reviewWords: reviewWords ?? this.reviewWords,
|
|||
|
|
todayStatistics: todayStatistics ?? this.todayStatistics,
|
|||
|
|
currentSession: currentSession ?? this.currentSession,
|
|||
|
|
dailyStats: dailyStats ?? this.dailyStats,
|
|||
|
|
overallStats: overallStats ?? this.overallStats,
|
|||
|
|
weeklyWordsStudied: weeklyWordsStudied ?? this.weeklyWordsStudied,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 词汇状态管理
|
|||
|
|
class VocabularyNotifier extends StateNotifier<VocabularyState> {
|
|||
|
|
final VocabularyService _vocabularyService;
|
|||
|
|
|
|||
|
|
VocabularyNotifier(this._vocabularyService) : super(const VocabularyState());
|
|||
|
|
|
|||
|
|
/// 加载系统词汇书
|
|||
|
|
Future<void> loadSystemVocabularyBooks({
|
|||
|
|
VocabularyBookDifficulty? difficulty,
|
|||
|
|
String? category,
|
|||
|
|
}) async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final books = await _vocabularyService.getSystemVocabularyBooks(
|
|||
|
|
difficulty: difficulty,
|
|||
|
|
category: category,
|
|||
|
|
limit: 100, // 加载所有词汇书
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
systemBooks: books,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载词汇书分类
|
|||
|
|
Future<void> loadVocabularyBookCategories() async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final categories = await _vocabularyService.getVocabularyBookCategories();
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
categories: categories,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载用户词汇书
|
|||
|
|
Future<void> loadUserVocabularyBooks() async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final books = await _vocabularyService.getUserVocabularyBooks();
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
userBooks: books,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载今日学习单词
|
|||
|
|
Future<void> loadTodayStudyWords({String? userId}) async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final words = await _vocabularyService.getTodayStudyWords();
|
|||
|
|
print('今日单词加载完成,数量: ${words.length}');
|
|||
|
|
|
|||
|
|
StudyStatistics statistics;
|
|||
|
|
try {
|
|||
|
|
statistics = await _vocabularyService.getStudyStatistics(DateTime.now());
|
|||
|
|
print('学习统计加载成功: wordsStudied=${statistics.wordsStudied}, newWords=${statistics.newWordsLearned}');
|
|||
|
|
} catch (e) {
|
|||
|
|
print('学习统计加载失败: $e,使用默认值');
|
|||
|
|
// 接口不可用时,构造默认的今日统计
|
|||
|
|
final now = DateTime.now();
|
|||
|
|
final correct = (words.length * 0.8).round();
|
|||
|
|
final wrong = words.length - correct;
|
|||
|
|
statistics = StudyStatistics(
|
|||
|
|
id: 'local_${now.toIso8601String().split('T').first}',
|
|||
|
|
userId: 'current_user',
|
|||
|
|
date: now,
|
|||
|
|
sessionCount: 1,
|
|||
|
|
wordsStudied: words.length,
|
|||
|
|
newWordsLearned: words.length,
|
|||
|
|
wordsReviewed: 0,
|
|||
|
|
wordsMastered: 0,
|
|||
|
|
totalStudyTimeSeconds: words.length * 30,
|
|||
|
|
correctAnswers: correct,
|
|||
|
|
wrongAnswers: wrong,
|
|||
|
|
averageAccuracy: words.isEmpty ? 0.0 : correct / (correct + wrong),
|
|||
|
|
experienceGained: words.length * 5,
|
|||
|
|
pointsGained: words.length * 2,
|
|||
|
|
streakDays: 1,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 额外加载每日词汇统计(wordsLearned、studyTimeMinutes)
|
|||
|
|
DailyStats? dailyStats;
|
|||
|
|
if (userId != null && userId.isNotEmpty) {
|
|||
|
|
try {
|
|||
|
|
dailyStats = await _vocabularyService.getDailyVocabularyStats(userId: userId);
|
|||
|
|
print('每日词汇统计加载成功: wordsLearned=${dailyStats.wordsLearned}');
|
|||
|
|
} catch (e) {
|
|||
|
|
print('每日词汇统计加载失败: $e');
|
|||
|
|
// ignore, 使用StudyStatistics中的wordsStudied作为兜底
|
|||
|
|
dailyStats = DailyStats(
|
|||
|
|
wordsLearned: statistics.wordsStudied,
|
|||
|
|
studyTimeMinutes: (statistics.totalStudyTimeSeconds / 60).round(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
todayWords: words,
|
|||
|
|
todayStatistics: statistics,
|
|||
|
|
dailyStats: dailyStats,
|
|||
|
|
);
|
|||
|
|
print('状态更新完成: todayWords=${words.length}, statistics.wordsStudied=${statistics.wordsStudied}');
|
|||
|
|
} catch (e) {
|
|||
|
|
print('loadTodayStudyWords失败: $e');
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载用户词汇整体统计(总学习词汇数、准确率等)
|
|||
|
|
Future<void> loadUserVocabularyOverallStats() async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
final stats = await _vocabularyService.getUserVocabularyStats();
|
|||
|
|
print('整体统计加载成功: $stats');
|
|||
|
|
state = state.copyWith(isLoading: false, overallStats: stats);
|
|||
|
|
} catch (e) {
|
|||
|
|
print('整体统计加载失败: $e');
|
|||
|
|
state = state.copyWith(isLoading: false, error: e.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载本周学习统计(累计学习单词数)
|
|||
|
|
Future<void> loadWeeklyStudyStats() async {
|
|||
|
|
try {
|
|||
|
|
final now = DateTime.now();
|
|||
|
|
final startOfWeek = now.subtract(Duration(days: now.weekday - 1)); // 周一
|
|||
|
|
final endOfWeek = startOfWeek.add(const Duration(days: 6)); // 周日
|
|||
|
|
print('加载本周统计: $startOfWeek 到 $endOfWeek');
|
|||
|
|
final list = await _vocabularyService.getStudyStatisticsHistory(
|
|||
|
|
startDate: DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day),
|
|||
|
|
endDate: DateTime(endOfWeek.year, endOfWeek.month, endOfWeek.day),
|
|||
|
|
);
|
|||
|
|
print('本周统计数据: ${list.length} 天');
|
|||
|
|
final total = list.fold<int>(0, (sum, s) => sum + (s.wordsStudied));
|
|||
|
|
print('本周累计学习: $total 个单词');
|
|||
|
|
state = state.copyWith(weeklyWordsStudied: total);
|
|||
|
|
} catch (e) {
|
|||
|
|
print('本周统计加载失败: $e');
|
|||
|
|
// 出错时保持为0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 加载复习单词
|
|||
|
|
Future<void> loadReviewWords() async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final words = await _vocabularyService.getReviewWords();
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
reviewWords: words,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 添加词汇书到用户
|
|||
|
|
Future<void> addVocabularyBookToUser(String bookId) async {
|
|||
|
|
try {
|
|||
|
|
await _vocabularyService.addVocabularyBookToUser(bookId);
|
|||
|
|
// 重新加载用户词汇书
|
|||
|
|
await loadUserVocabularyBooks();
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(error: e.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 开始学习会话
|
|||
|
|
Future<void> startStudySession({
|
|||
|
|
required StudyMode mode,
|
|||
|
|
String? vocabularyBookId,
|
|||
|
|
required int targetWordCount,
|
|||
|
|
}) async {
|
|||
|
|
try {
|
|||
|
|
state = state.copyWith(isLoading: true, error: null);
|
|||
|
|
|
|||
|
|
final session = await _vocabularyService.startStudySession(
|
|||
|
|
mode: mode,
|
|||
|
|
vocabularyBookId: vocabularyBookId,
|
|||
|
|
targetWordCount: targetWordCount,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
currentSession: session,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(
|
|||
|
|
isLoading: false,
|
|||
|
|
error: e.toString(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 结束学习会话
|
|||
|
|
Future<void> endStudySession({
|
|||
|
|
required String sessionId,
|
|||
|
|
required int durationSeconds,
|
|||
|
|
required List<WordExerciseRecord> exercises,
|
|||
|
|
}) async {
|
|||
|
|
try {
|
|||
|
|
await _vocabularyService.endStudySession(
|
|||
|
|
sessionId,
|
|||
|
|
durationSeconds: durationSeconds,
|
|||
|
|
exercises: exercises,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
state = state.copyWith(currentSession: null);
|
|||
|
|
|
|||
|
|
// 重新加载今日数据
|
|||
|
|
await loadTodayStudyWords();
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(error: e.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 更新单词学习进度
|
|||
|
|
Future<void> updateWordProgress({
|
|||
|
|
required String wordId,
|
|||
|
|
required LearningStatus status,
|
|||
|
|
required bool isCorrect,
|
|||
|
|
int responseTime = 0,
|
|||
|
|
}) async {
|
|||
|
|
try {
|
|||
|
|
await _vocabularyService.updateUserWordProgress(
|
|||
|
|
wordId: wordId,
|
|||
|
|
status: status,
|
|||
|
|
isCorrect: isCorrect,
|
|||
|
|
responseTime: responseTime,
|
|||
|
|
);
|
|||
|
|
} catch (e) {
|
|||
|
|
state = state.copyWith(error: e.toString());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 清除错误
|
|||
|
|
void clearError() {
|
|||
|
|
state = state.copyWith(error: null);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 词汇服务提供者
|
|||
|
|
final vocabularyServiceProvider = FutureProvider<VocabularyService>((ref) async {
|
|||
|
|
final apiClient = ApiClient.instance;
|
|||
|
|
final storageService = await StorageService.getInstance();
|
|||
|
|
return VocabularyService(
|
|||
|
|
apiClient: apiClient,
|
|||
|
|
storageService: storageService,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 词汇状态提供者(使用 StateNotifierProvider 以支持状态更新)
|
|||
|
|
final vocabularyProvider = StateNotifierProvider.autoDispose<VocabularyNotifier, VocabularyState>((ref) {
|
|||
|
|
// 同步创建 Notifier,延迟加载数据
|
|||
|
|
final vocabularyService = ref.watch(vocabularyServiceProvider).value;
|
|||
|
|
if (vocabularyService == null) {
|
|||
|
|
// 如果服务未就绪,返回空状态的 Notifier
|
|||
|
|
throw Exception('词汇服务未就绪');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final notifier = VocabularyNotifier(vocabularyService);
|
|||
|
|
|
|||
|
|
// 在后台异步加载系统词汇书和分类
|
|||
|
|
Future.wait([
|
|||
|
|
notifier.loadSystemVocabularyBooks(),
|
|||
|
|
notifier.loadVocabularyBookCategories(),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
return notifier;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 当前学习会话提供者
|
|||
|
|
final currentStudySessionProvider = Provider<StudySession?>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.currentSession;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 今日学习统计提供者
|
|||
|
|
final todayStatisticsProvider = Provider<StudyStatistics?>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.todayStatistics;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 每日词汇统计提供者
|
|||
|
|
final dailyVocabularyStatsProvider = Provider<DailyStats?>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.dailyStats;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 用户词汇整体统计提供者
|
|||
|
|
final overallVocabularyStatsProvider = Provider<Map<String, dynamic>?>(
|
|||
|
|
(ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.overallStats;
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
/// 本周累计学习单词数提供者
|
|||
|
|
final weeklyWordsStudiedProvider = Provider<int>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.weeklyWordsStudied;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 用户词汇书提供者
|
|||
|
|
final userVocabularyBooksProvider = Provider<List<VocabularyBook>>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.userBooks;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 系统词汇书提供者
|
|||
|
|
final systemVocabularyBooksProvider = Provider<List<VocabularyBook>>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.systemBooks;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 今日单词提供者
|
|||
|
|
final todayWordsProvider = Provider<List<Word>>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.todayWords;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 复习单词提供者
|
|||
|
|
final reviewWordsProvider = Provider<List<Word>>((ref) {
|
|||
|
|
final state = ref.watch(vocabularyProvider);
|
|||
|
|
return state.reviewWords;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 词汇书学习统计
|
|||
|
|
class BookStudyStats {
|
|||
|
|
final String bookId;
|
|||
|
|
final int studyDays;
|
|||
|
|
final double averageAccuracy;
|
|||
|
|
|
|||
|
|
const BookStudyStats({
|
|||
|
|
required this.bookId,
|
|||
|
|
required this.studyDays,
|
|||
|
|
required this.averageAccuracy,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 学习统计服务提供者
|
|||
|
|
final learningStatsServiceProvider = FutureProvider<LearningStatsService>((ref) async {
|
|||
|
|
final apiClient = ApiClient.instance;
|
|||
|
|
final storageService = await StorageService.getInstance();
|
|||
|
|
return LearningStatsService(apiClient: apiClient, storageService: storageService);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 用户学习统计提供者
|
|||
|
|
final learningStatsProvider = FutureProvider<LearningStats>((ref) async {
|
|||
|
|
final service = await ref.watch(learningStatsServiceProvider.future);
|
|||
|
|
return service.getUserStats();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 指定词汇书的学习统计(学习天数、平均准确率)
|
|||
|
|
final bookStudyStatsProvider = FutureProvider.autoDispose.family<BookStudyStats, String>((ref, bookId) async {
|
|||
|
|
final service = await ref.watch(learningStatsServiceProvider.future);
|
|||
|
|
final records = await service.getDailyRecords();
|
|||
|
|
final filtered = records.where((r) => r.vocabularyBookIds.contains(bookId)).toList();
|
|||
|
|
final studyDays = filtered.length;
|
|||
|
|
double avgAccuracy = 0.0;
|
|||
|
|
if (filtered.isNotEmpty) {
|
|||
|
|
final totalWeight = filtered.fold<int>(0, (sum, r) => sum + (r.wordsLearned + r.wordsReviewed));
|
|||
|
|
if (totalWeight > 0) {
|
|||
|
|
final weightedSum = filtered.fold<double>(0.0, (sum, r) => sum + r.accuracyRate * (r.wordsLearned + r.wordsReviewed));
|
|||
|
|
avgAccuracy = weightedSum / totalWeight;
|
|||
|
|
} else {
|
|||
|
|
avgAccuracy = filtered.fold<double>(0.0, (sum, r) => sum + r.accuracyRate) / filtered.length;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return BookStudyStats(bookId: bookId, studyDays: studyDays, averageAccuracy: avgAccuracy);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 词汇书学习进度(百分比、已学/已掌握数量)
|
|||
|
|
final vocabularyBookProgressProvider = FutureProvider.autoDispose.family<UserVocabularyBookProgress, String>((ref, bookId) async {
|
|||
|
|
final service = await ref.watch(vocabularyServiceProvider.future);
|
|||
|
|
return service.getVocabularyBookProgress(bookId);
|
|||
|
|
});
|