Flutter 数据层 (Phase F1): - journal_entry.dart: 日记数据模型 (Mood/Weather/tags/version) - journal_element.dart: 元素模型 (text/image/sticker/handwriting_ref/tape) - school_class.dart: 班级模型 - user_settings.dart: 用户设置 (主题/画笔/字号) - isar_database.dart: Isar 初始化 - api_client.dart: Dio + JWT注入 + 离线感知 + 401处理 - journal_repository.dart: 抽象接口 + InMemory实现 (乐观锁) - sync_engine.dart: WiFi同步 + 操作队列 + 重试(5次) + 快照持久化 Rust 班级系统 (Phase B3): - class_service.rs: 创建班级(6位码) + 加入班级 + 成员管理 - topic_service.rs: 老师布置主题 + 主题列表 - comment_service.rs: 老师点评 + 评语列表 - class_handler.rs: 5个API端点 + 权限守卫 - topic_handler.rs: 2个API端点 - comment_handler.rs: 2个API端点 - dto.rs: 新增5个DTO (ClassMemberResp/CreateTopicReq/TopicResp/CreateCommentReq/CommentResp) - 6条新路由注册 验证: cargo check 通过, 433测试全绿, flutter analyze 1 warning
157 lines
4.7 KiB
Dart
157 lines
4.7 KiB
Dart
// 日记元素数据模型 — 手写不可变类(避免 build_runner 依赖)
|
|
|
|
import 'dart:collection';
|
|
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
/// 元素类型 — 日记页面上的各种内容载体
|
|
enum ElementType {
|
|
text('text'),
|
|
image('image'),
|
|
sticker('sticker'),
|
|
handwritingRef('handwriting_ref'),
|
|
tape('tape');
|
|
|
|
const ElementType(this.value);
|
|
final String value;
|
|
}
|
|
|
|
/// 日记元素 — 日记页面中的单个内容单元
|
|
///
|
|
/// 每个元素有独立的位置、尺寸、旋转和层级。
|
|
/// [content] 字段根据 [elementType] 存储不同的结构化数据:
|
|
/// - text: {'text': '...', 'fontSize': 16.0, 'fontColor': '#2D2420'}
|
|
/// - image: {'filePath': '...', 'thumbnailPath': '...'}
|
|
/// - sticker: {'stickerPackId': '...', 'stickerId': '...'}
|
|
/// - handwriting_ref: {'strokesFileId': '...', 'strokeCount': 42}
|
|
/// - tape: {'tapeStyle': 'washi_dots', 'tapeColor': '#F2CC8F'}
|
|
class JournalElement {
|
|
final String id;
|
|
final String journalId;
|
|
final ElementType elementType;
|
|
final double positionX;
|
|
final double positionY;
|
|
final double width;
|
|
final double height;
|
|
final double rotation;
|
|
final int zIndex;
|
|
final Map<String, dynamic> content;
|
|
final int version;
|
|
final DateTime createdAt;
|
|
final DateTime updatedAt;
|
|
|
|
const JournalElement({
|
|
required this.id,
|
|
required this.journalId,
|
|
required this.elementType,
|
|
this.positionX = 0,
|
|
this.positionY = 0,
|
|
this.width = 100,
|
|
this.height = 100,
|
|
this.rotation = 0,
|
|
this.zIndex = 0,
|
|
this.content = const {},
|
|
this.version = 1,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
});
|
|
|
|
JournalElement copyWith({
|
|
String? id,
|
|
String? journalId,
|
|
ElementType? elementType,
|
|
double? positionX,
|
|
double? positionY,
|
|
double? width,
|
|
double? height,
|
|
double? rotation,
|
|
int? zIndex,
|
|
Map<String, dynamic>? content,
|
|
int? version,
|
|
DateTime? createdAt,
|
|
DateTime? updatedAt,
|
|
}) =>
|
|
JournalElement(
|
|
id: id ?? this.id,
|
|
journalId: journalId ?? this.journalId,
|
|
elementType: elementType ?? this.elementType,
|
|
positionX: positionX ?? this.positionX,
|
|
positionY: positionY ?? this.positionY,
|
|
width: width ?? this.width,
|
|
height: height ?? this.height,
|
|
rotation: rotation ?? this.rotation,
|
|
zIndex: zIndex ?? this.zIndex,
|
|
content: content ?? this.content,
|
|
version: version ?? this.version,
|
|
createdAt: createdAt ?? this.createdAt,
|
|
updatedAt: updatedAt ?? this.updatedAt,
|
|
);
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'journal_id': journalId,
|
|
'element_type': elementType.value,
|
|
'position_x': positionX,
|
|
'position_y': positionY,
|
|
'width': width,
|
|
'height': height,
|
|
'rotation': rotation,
|
|
'z_index': zIndex,
|
|
'content': content,
|
|
'version': version,
|
|
'created_at': createdAt.toIso8601String(),
|
|
'updated_at': updatedAt.toIso8601String(),
|
|
};
|
|
|
|
factory JournalElement.fromJson(Map<String, dynamic> json) => JournalElement(
|
|
id: json['id'] as String,
|
|
journalId: json['journal_id'] as String,
|
|
elementType: ElementType.values.firstWhere(
|
|
(e) => e.value == json['element_type'],
|
|
orElse: () => ElementType.text,
|
|
),
|
|
positionX: (json['position_x'] as num?)?.toDouble() ?? 0,
|
|
positionY: (json['position_y'] as num?)?.toDouble() ?? 0,
|
|
width: (json['width'] as num?)?.toDouble() ?? 100,
|
|
height: (json['height'] as num?)?.toDouble() ?? 100,
|
|
rotation: (json['rotation'] as num?)?.toDouble() ?? 0,
|
|
zIndex: (json['z_index'] as int?) ?? 0,
|
|
content: UnmodifiableMapView(
|
|
Map<String, dynamic>.from(json['content'] as Map? ?? {}),
|
|
),
|
|
version: (json['version'] as int?) ?? 1,
|
|
createdAt: DateTime.parse(json['created_at'] as String),
|
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
|
);
|
|
|
|
/// 创建新元素的工厂方法 — 自动生成 id 和时间戳
|
|
factory JournalElement.create({
|
|
required String journalId,
|
|
required ElementType elementType,
|
|
double positionX = 0,
|
|
double positionY = 0,
|
|
double width = 100,
|
|
double height = 100,
|
|
double rotation = 0,
|
|
int zIndex = 0,
|
|
Map<String, dynamic> content = const {},
|
|
}) {
|
|
final now = DateTime.now();
|
|
return JournalElement(
|
|
id: const Uuid().v4(),
|
|
journalId: journalId,
|
|
elementType: elementType,
|
|
positionX: positionX,
|
|
positionY: positionY,
|
|
width: width,
|
|
height: height,
|
|
rotation: rotation,
|
|
zIndex: zIndex,
|
|
content: content,
|
|
version: 1,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
);
|
|
}
|
|
}
|