Files
nj/app/lib/features/templates/bloc/template_bloc.dart
iven 8331db63ba feat(app): 设置页 UI + Mood/成就/贴纸 BLoC 接入 API + B7 测试扩展
前端改动:
- 新建设置页面 (主题切换/关于/隐私政策/用户协议/儿童隐私保护)
- SettingsBloc 注册到 MultiRepositoryProvider 全局可访问
- MoodBloc 修复编译错误 + 接入 /diary/stats/mood API
- MoodPage 添加错误状态展示和重试按钮
- AchievementBloc + 页面改造接入 /diary/achievements API
- StickerBloc + 页面改造接入 /diary/sticker-packs API
- TemplateBloc + 页面改造接入 /diary/templates API
- ProfilePage 设置入口改为跳转 /settings
- 添加 /settings 路由

后端改动:
- 扩展 mood_stats_service 测试 (连续天数算法/心情计数/边界场景)
- 新增 class_service 测试 (班级码生成/唯一性/错误映射)
- 新增 achievement_service 测试 (DTO 结构/序列化/map 构建)
- 新增 sticker_service 测试 (DTO 序列化/错误处理)
- 扩展 dto.rs 测试 (achievement/mood_stats/sticker/template/notification)
- 清理 2 个 unused import warning

验证:
- cargo check 0 error 0 warning
- flutter analyze 0 error
2026-06-01 11:19:43 +08:00

136 lines
3.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 模板 BLoC — 通过 API 加载模板列表
import 'package:flutter/material.dart';
import 'package:nuanji_app/data/remote/api_client.dart';
// ===== 模型 =====
/// 日记模板
class Template {
final String id;
final String name;
final String? description;
final String? previewUrl;
final Map<String, dynamic>? templateData;
final String? category;
final bool isFree;
/// 用于 UI 显示的 emoji基于 category 推导)
String get emoji => switch (category) {
'日常' => '☀️',
'旅行' => '🗺️',
'校园' => '📚',
'节日' => '🎄',
'创意' => '',
_ => '📝',
};
const Template({
required this.id,
required this.name,
this.description,
this.previewUrl,
this.templateData,
this.category,
this.isFree = true,
});
}
// ===== State =====
/// 模板页面状态
class TemplateState {
final List<Template> templates;
final String selectedCategory;
final bool isLoading;
final String? errorMessage;
const TemplateState({
this.templates = const [],
this.selectedCategory = '全部',
this.isLoading = false,
this.errorMessage,
});
/// 按分类过滤模板
List<Template> get filteredTemplates => selectedCategory == '全部'
? templates
: templates.where((t) => t.category == selectedCategory).toList();
/// 所有分类(去重 + 加"全部"
List<String> get categories {
final cats = templates
.map((t) => t.category)
.whereType<String>()
.toSet()
.toList();
return ['全部', ...cats];
}
TemplateState copyWith({
List<Template>? templates,
String? selectedCategory,
bool? isLoading,
String? errorMessage,
}) =>
TemplateState(
templates: templates ?? this.templates,
selectedCategory: selectedCategory ?? this.selectedCategory,
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
);
}
// ===== BLoC =====
/// 模板 BLoC — ChangeNotifier 模式
class TemplateBloc extends ChangeNotifier {
final ApiClient _api;
TemplateState _state = const TemplateState();
TemplateState get state => _state;
TemplateBloc({required ApiClient api}) : _api = api;
/// 加载模板列表
void load() {
_state = _state.copyWith(isLoading: true);
notifyListeners();
_fetchTemplates();
}
/// 选择分类
void selectCategory(String category) {
_state = _state.copyWith(selectedCategory: category);
notifyListeners();
}
Future<void> _fetchTemplates() async {
try {
final response = await _api.get('/diary/templates');
final body = response.data as Map<String, dynamic>;
final list = body['data'] as List? ?? [];
final templates = list.map((item) {
final m = item as Map<String, dynamic>;
return Template(
id: m['id'] as String,
name: m['name'] as String,
description: m['description'] as String?,
previewUrl: m['preview_url'] as String?,
templateData: m['template_data'] as Map<String, dynamic>?,
category: m['category'] as String?,
isFree: m['is_free'] as bool? ?? true,
);
}).toList();
_state = _state.copyWith(isLoading: false, templates: templates);
} catch (e) {
_state = _state.copyWith(
isLoading: false,
errorMessage: '加载模板列表失败',
);
}
notifyListeners();
}
}