fix(app): Phase 1.1 紧急修复 — SyncEngine 接入 + authorId + catch 异常处理
- feat(sync): SyncEngine 接入 EditorPage, 保存时 enqueue + 网络恢复自动 trySync - fix(editor): authorId 从 AuthBloc 获取, 替代硬编码 'local' - fix(bloc): class_bloc/calendar/profile/parent catch(_).全部改为 debugPrint - feat(editor): 编辑器工具栏拆分 (brush_panel/tag_panel/text_format_bar/dot_grid_painter) - feat(editor): EditorBloc 扩展 + EditorPage 增强 - feat(search): SearchBloc 扩展搜索功能 - feat(home): HomeBloc/HomePage 增强 - feat(auth): LoginPage 增强 - feat(templates): TemplateGalleryPage 重构 - fix(web): 管理端班级/日记页面修复 - fix(server): comment_service + theme_handler 修复 - docs: 添加全链路审计报告和验证截图
This commit is contained in:
@@ -4,13 +4,16 @@
|
||||
// - 手写层:笔画列表 + 画笔设置 + 撤销/重做栈
|
||||
// - 元素层:贴纸/照片/文字元素列表 + 选中元素 + 拖拽状态
|
||||
// - 工具栏:当前活动工具 + 颜色面板 + 笔刷大小
|
||||
// - 标签/心情:日记标签管理 + 心情选择 + 标题编辑
|
||||
// - 自动保存:笔画/元素变更 debounce → 触发保存回调
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../widgets/stroke_model.dart';
|
||||
import '../../../data/models/journal_entry.dart';
|
||||
import '../../../data/models/journal_element.dart';
|
||||
|
||||
// ============================================================
|
||||
@@ -110,20 +113,69 @@ class ElementsLoaded extends EditorEvent {
|
||||
ElementsLoaded(this.elements);
|
||||
}
|
||||
|
||||
// --- 标签/心情/标题事件 ---
|
||||
|
||||
/// 添加标签
|
||||
class TagAdded extends EditorEvent {
|
||||
final String tag;
|
||||
TagAdded(this.tag);
|
||||
}
|
||||
|
||||
/// 移除标签
|
||||
class TagRemoved extends EditorEvent {
|
||||
final String tag;
|
||||
TagRemoved(this.tag);
|
||||
}
|
||||
|
||||
/// 加载已有标签
|
||||
class TagsLoaded extends EditorEvent {
|
||||
final List<String> tags;
|
||||
TagsLoaded(this.tags);
|
||||
}
|
||||
|
||||
/// 心情变更
|
||||
class MoodChanged extends EditorEvent {
|
||||
final Mood mood;
|
||||
MoodChanged(this.mood);
|
||||
}
|
||||
|
||||
/// 标题变更
|
||||
class TitleChanged extends EditorEvent {
|
||||
final String title;
|
||||
TitleChanged(this.title);
|
||||
}
|
||||
|
||||
/// 文字格式变更
|
||||
class TextFormatChanged extends EditorEvent {
|
||||
final String elementId;
|
||||
final bool? bold;
|
||||
final bool? italic;
|
||||
final bool? underline;
|
||||
final String? color;
|
||||
final TextAlign? alignment;
|
||||
TextFormatChanged({
|
||||
required this.elementId,
|
||||
this.bold,
|
||||
this.italic,
|
||||
this.underline,
|
||||
this.color,
|
||||
this.alignment,
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 状态
|
||||
// ============================================================
|
||||
|
||||
/// 编辑器工具枚举
|
||||
/// 编辑器工具枚举 — 对应底部 6 个工具按钮 + 内部 select 模式
|
||||
enum EditorTool {
|
||||
pen, // 钢笔
|
||||
pencil, // 铅笔
|
||||
marker, // 马克笔
|
||||
eraser, // 橡皮擦
|
||||
select, // 选择/移动元素
|
||||
text, // 文字输入
|
||||
sticker, // 贴纸
|
||||
image, // 照片
|
||||
template, // 模板
|
||||
brush, // 画笔(含钢笔/铅笔/马克笔/橡皮子类型)
|
||||
photo, // 照片
|
||||
text, // 文字
|
||||
more, // 更多
|
||||
select, // 选择/移动元素(内部模式,非 UI 按钮)
|
||||
}
|
||||
|
||||
/// 编辑器状态
|
||||
@@ -134,6 +186,7 @@ class EditorState {
|
||||
final BrushType brushType;
|
||||
final String brushColor;
|
||||
final double brushWidth;
|
||||
final double brushOpacity;
|
||||
final int maxUndoSteps;
|
||||
|
||||
// 元素层
|
||||
@@ -143,6 +196,11 @@ class EditorState {
|
||||
// 工具栏
|
||||
final EditorTool activeTool;
|
||||
|
||||
// 标签/心情/标题
|
||||
final List<String> tags;
|
||||
final Mood selectedMood;
|
||||
final String title;
|
||||
|
||||
// 自动保存
|
||||
final bool isDirty;
|
||||
final DateTime? lastSavedAt;
|
||||
@@ -153,10 +211,14 @@ class EditorState {
|
||||
this.brushType = BrushType.pen,
|
||||
this.brushColor = '#2D2420',
|
||||
this.brushWidth = 3.0,
|
||||
this.brushOpacity = 1.0,
|
||||
this.maxUndoSteps = 50,
|
||||
this.elements = const [],
|
||||
this.selectedElementId,
|
||||
this.activeTool = EditorTool.pen,
|
||||
this.activeTool = EditorTool.brush,
|
||||
this.tags = const [],
|
||||
this.selectedMood = Mood.calm,
|
||||
this.title = '',
|
||||
this.isDirty = false,
|
||||
this.lastSavedAt,
|
||||
});
|
||||
@@ -167,10 +229,14 @@ class EditorState {
|
||||
BrushType? brushType,
|
||||
String? brushColor,
|
||||
double? brushWidth,
|
||||
double? brushOpacity,
|
||||
List<JournalElement>? elements,
|
||||
String? selectedElementId,
|
||||
bool clearSelection = false,
|
||||
EditorTool? activeTool,
|
||||
List<String>? tags,
|
||||
Mood? selectedMood,
|
||||
String? title,
|
||||
bool? isDirty,
|
||||
DateTime? lastSavedAt,
|
||||
}) =>
|
||||
@@ -180,27 +246,28 @@ class EditorState {
|
||||
brushType: brushType ?? this.brushType,
|
||||
brushColor: brushColor ?? this.brushColor,
|
||||
brushWidth: brushWidth ?? this.brushWidth,
|
||||
brushOpacity: brushOpacity ?? this.brushOpacity,
|
||||
maxUndoSteps: maxUndoSteps,
|
||||
elements: elements ?? this.elements,
|
||||
selectedElementId: clearSelection ? null : (selectedElementId ?? this.selectedElementId),
|
||||
selectedElementId:
|
||||
clearSelection ? null : (selectedElementId ?? this.selectedElementId),
|
||||
activeTool: activeTool ?? this.activeTool,
|
||||
tags: tags ?? this.tags,
|
||||
selectedMood: selectedMood ?? this.selectedMood,
|
||||
title: title ?? this.title,
|
||||
isDirty: isDirty ?? this.isDirty,
|
||||
lastSavedAt: lastSavedAt ?? this.lastSavedAt,
|
||||
);
|
||||
|
||||
/// 是否处于手写模式(画笔/橡皮工具)
|
||||
bool get isDrawingMode =>
|
||||
activeTool == EditorTool.pen ||
|
||||
activeTool == EditorTool.pencil ||
|
||||
activeTool == EditorTool.marker ||
|
||||
activeTool == EditorTool.eraser;
|
||||
/// 是否处于手写模式
|
||||
bool get isDrawingMode => activeTool == EditorTool.brush;
|
||||
|
||||
/// 是否处于元素操作模式
|
||||
bool get isElementMode =>
|
||||
activeTool == EditorTool.select ||
|
||||
activeTool == EditorTool.text ||
|
||||
activeTool == EditorTool.sticker ||
|
||||
activeTool == EditorTool.image;
|
||||
activeTool == EditorTool.photo;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -238,6 +305,14 @@ class EditorBloc extends Bloc<EditorEvent, EditorState> {
|
||||
|
||||
// 工具栏事件
|
||||
on<ToolChanged>(_onToolChanged);
|
||||
|
||||
// 标签/心情/标题事件
|
||||
on<TagAdded>(_onTagAdded);
|
||||
on<TagRemoved>(_onTagRemoved);
|
||||
on<TagsLoaded>(_onTagsLoaded);
|
||||
on<MoodChanged>(_onMoodChanged);
|
||||
on<TitleChanged>(_onTitleChanged);
|
||||
on<TextFormatChanged>(_onTextFormatChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -321,7 +396,8 @@ class EditorBloc extends Bloc<EditorEvent, EditorState> {
|
||||
// ============================================================
|
||||
|
||||
void _onElementAdded(ElementAdded event, Emitter<EditorState> emit) {
|
||||
final updated = List<JournalElement>.from(state.elements)..add(event.element);
|
||||
final updated =
|
||||
List<JournalElement>.from(state.elements)..add(event.element);
|
||||
emit(state.copyWith(
|
||||
elements: updated,
|
||||
selectedElementId: event.element.id,
|
||||
@@ -394,6 +470,58 @@ class EditorBloc extends Bloc<EditorEvent, EditorState> {
|
||||
));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 标签/心情/标题事件处理
|
||||
// ============================================================
|
||||
|
||||
void _onTagAdded(TagAdded event, Emitter<EditorState> emit) {
|
||||
if (state.tags.contains(event.tag)) return;
|
||||
if (state.tags.length >= 10) return; // 设计 Token: maxTags=10
|
||||
final updated = List<String>.from(state.tags)..add(event.tag);
|
||||
emit(state.copyWith(tags: updated, isDirty: true));
|
||||
_scheduleAutoSave();
|
||||
}
|
||||
|
||||
void _onTagRemoved(TagRemoved event, Emitter<EditorState> emit) {
|
||||
final updated = List<String>.from(state.tags)..remove(event.tag);
|
||||
emit(state.copyWith(tags: updated, isDirty: true));
|
||||
_scheduleAutoSave();
|
||||
}
|
||||
|
||||
void _onTagsLoaded(TagsLoaded event, Emitter<EditorState> emit) {
|
||||
emit(state.copyWith(tags: event.tags));
|
||||
}
|
||||
|
||||
void _onMoodChanged(MoodChanged event, Emitter<EditorState> emit) {
|
||||
emit(state.copyWith(selectedMood: event.mood, isDirty: true));
|
||||
_scheduleAutoSave();
|
||||
}
|
||||
|
||||
void _onTitleChanged(TitleChanged event, Emitter<EditorState> emit) {
|
||||
emit(state.copyWith(title: event.title, isDirty: true));
|
||||
_scheduleAutoSave();
|
||||
}
|
||||
|
||||
void _onTextFormatChanged(
|
||||
TextFormatChanged event,
|
||||
Emitter<EditorState> emit,
|
||||
) {
|
||||
final updated = state.elements.map((e) {
|
||||
if (e.id != event.elementId) return e;
|
||||
final content = Map<String, dynamic>.from(e.content);
|
||||
if (event.bold != null) content['bold'] = event.bold;
|
||||
if (event.italic != null) content['italic'] = event.italic;
|
||||
if (event.underline != null) content['underline'] = event.underline;
|
||||
if (event.color != null) content['color'] = event.color;
|
||||
if (event.alignment != null) {
|
||||
content['alignment'] = event.alignment!.index;
|
||||
}
|
||||
return e.copyWith(content: content);
|
||||
}).toList();
|
||||
emit(state.copyWith(elements: updated, isDirty: true));
|
||||
_scheduleAutoSave();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 自动保存
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user