马良AI写作初始化仓库
This commit is contained in:
950
AINoval/lib/models/novel_structure.dart
Normal file
950
AINoval/lib/models/novel_structure.dart
Normal file
@@ -0,0 +1,950 @@
|
||||
import 'package:ainoval/utils/logger.dart';
|
||||
|
||||
/// 小说模型
|
||||
class Novel {
|
||||
Novel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.coverUrl= '',
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.acts = const [],
|
||||
this.lastEditedChapterId,
|
||||
this.author,
|
||||
this.wordCount = 0,
|
||||
this.readTime = 0,
|
||||
this.version = 1,
|
||||
this.contributors = const <String>[],
|
||||
});
|
||||
|
||||
/// 从JSON创建Novel实例
|
||||
factory Novel.fromJson(Map<String, dynamic> json) {
|
||||
AppLogger.v(
|
||||
'NovelModel', 'Parsing Novel from JSON: ${json['id']}'); // 添加日志确认进入
|
||||
try {
|
||||
// --- 这是关键部分 ---
|
||||
List<Act> parsedActs = [];
|
||||
|
||||
// 处理acts数据 - 优先检查structure.acts路径
|
||||
if (json.containsKey('structure') && json['structure'] is Map) {
|
||||
final structure = json['structure'] as Map<String, dynamic>;
|
||||
|
||||
if (structure.containsKey('acts') && structure['acts'] is List) {
|
||||
AppLogger.v('NovelModel',
|
||||
'Found "structure.acts" list with ${(structure['acts'] as List).length} items.');
|
||||
|
||||
parsedActs = (structure['acts'] as List)
|
||||
.map((actJson) {
|
||||
if (actJson is Map<String, dynamic>) {
|
||||
// 对列表中的每个元素调用 Act.fromJson
|
||||
return Act.fromJson(actJson);
|
||||
} else {
|
||||
// 处理无效数据项
|
||||
AppLogger.w('NovelModel',
|
||||
'Invalid item in "structure.acts" list: $actJson');
|
||||
return null; // 返回null让whereType过滤掉
|
||||
}
|
||||
})
|
||||
.whereType<Act>() // 过滤掉可能的 null 值
|
||||
.toList();
|
||||
|
||||
AppLogger.v('NovelModel',
|
||||
'Successfully parsed ${parsedActs.length} acts from structure.acts.');
|
||||
} else {
|
||||
AppLogger.w('NovelModel',
|
||||
'"structure.acts" field is missing, null, or not a list in JSON for Novel ${json['id']}');
|
||||
}
|
||||
}
|
||||
// 如果在structure中没有找到有效的acts,尝试直接从json的acts字段读取
|
||||
else if (json.containsKey('acts') && json['acts'] is List) {
|
||||
AppLogger.v('NovelModel',
|
||||
'Found direct "acts" list with ${(json['acts'] as List).length} items.');
|
||||
|
||||
parsedActs = (json['acts'] as List)
|
||||
.map((actJson) {
|
||||
if (actJson is Map<String, dynamic>) {
|
||||
return Act.fromJson(actJson);
|
||||
} else {
|
||||
AppLogger.w('NovelModel',
|
||||
'Invalid item in direct "acts" list: $actJson');
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.whereType<Act>()
|
||||
.toList();
|
||||
|
||||
AppLogger.v('NovelModel',
|
||||
'Successfully parsed ${parsedActs.length} acts from direct acts field.');
|
||||
} else {
|
||||
AppLogger.w('NovelModel',
|
||||
'No valid acts field found in JSON for Novel ${json['id']}');
|
||||
}
|
||||
// --- 关键部分结束 ---
|
||||
|
||||
// 解析元数据
|
||||
final metadata = json['metadata'] as Map<String, dynamic>? ?? {};
|
||||
final wordCount = metadata['wordCount'] is int ? metadata['wordCount'] as int : 0;
|
||||
final readTime = metadata['readTime'] is int ? metadata['readTime'] as int : 0;
|
||||
final version = metadata['version'] is int ? metadata['version'] as int : 1;
|
||||
|
||||
// 处理contributors列表
|
||||
List<String> contributors = [];
|
||||
if (metadata.containsKey('contributors') && metadata['contributors'] is List) {
|
||||
// 尝试转换每个元素为String
|
||||
for (var item in metadata['contributors'] as List) {
|
||||
if (item is String) {
|
||||
contributors.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 解析日期
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
|
||||
try {
|
||||
createdAt = json.containsKey('createdAt') && json['createdAt'] is String
|
||||
? DateTime.parse(json['createdAt'] as String)
|
||||
: DateTime.now();
|
||||
} catch (e) {
|
||||
AppLogger.w('NovelModel', '解析createdAt失败,使用当前时间', e);
|
||||
createdAt = DateTime.now();
|
||||
}
|
||||
|
||||
try {
|
||||
updatedAt = json.containsKey('updatedAt') && json['updatedAt'] is String
|
||||
? DateTime.parse(json['updatedAt'] as String)
|
||||
: DateTime.now();
|
||||
} catch (e) {
|
||||
AppLogger.w('NovelModel', '解析updatedAt失败,使用当前时间', e);
|
||||
updatedAt = DateTime.now();
|
||||
}
|
||||
|
||||
// 处理封面URL字段
|
||||
String coverUrl = '';
|
||||
if (json.containsKey('coverUrl') && json['coverUrl'] is String) {
|
||||
coverUrl = json['coverUrl'] as String;
|
||||
} else if (json.containsKey('coverImage') && json['coverImage'] is String) {
|
||||
// 兼容后端可能使用coverImage字段
|
||||
coverUrl = json['coverImage'] as String;
|
||||
}
|
||||
|
||||
// 创建Novel对象
|
||||
return Novel(
|
||||
id: json['id'] as String? ?? 'unknown_${DateTime.now().millisecondsSinceEpoch}',
|
||||
title: json['title'] as String? ?? '无标题',
|
||||
coverUrl: coverUrl,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
acts: parsedActs,
|
||||
lastEditedChapterId: json['lastEditedChapterId'] as String?,
|
||||
author: json['author'] != null
|
||||
? Author.fromJson(json['author'] as Map<String, dynamic>)
|
||||
: null,
|
||||
wordCount: wordCount,
|
||||
readTime: readTime,
|
||||
version: version,
|
||||
contributors: contributors,
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('NovelModel', 'Error parsing Novel from JSON: ${json['id']}',
|
||||
e, stackTrace);
|
||||
// 返回一个基本的空Novel对象,避免应用崩溃
|
||||
return Novel(
|
||||
id: json['id'] as String? ?? 'error_${DateTime.now().millisecondsSinceEpoch}',
|
||||
title: '解析错误 - ${json['title'] ?? '无标题'}',
|
||||
coverUrl: '',
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
acts: [],
|
||||
wordCount: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
final String id;
|
||||
final String title;
|
||||
final String coverUrl;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final List<Act> acts;
|
||||
final String? lastEditedChapterId; // 上次编辑的章节ID
|
||||
final Author? author; // 作者信息
|
||||
final int wordCount; // 总字数(来自元数据)
|
||||
final int readTime; // 估计阅读时间(分钟)
|
||||
final int version; // 文档版本号
|
||||
final List<String> contributors; // 贡献者列表
|
||||
|
||||
/// 计算小说总字数(如果需要动态计算)
|
||||
int calculateWordCount() {
|
||||
int totalWordCount = 0;
|
||||
for (final act in acts) {
|
||||
for (final chapter in act.chapters) {
|
||||
for (final scene in chapter.scenes) {
|
||||
totalWordCount += scene.wordCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalWordCount;
|
||||
}
|
||||
|
||||
/// 计算小说总场景数(考虑 sceneIds 字段)
|
||||
int getSceneCount() {
|
||||
int totalSceneCount = 0;
|
||||
//AppLogger.d('Novel', '开始计算场景总数');
|
||||
|
||||
for (final act in acts) {
|
||||
int actSceneCount = 0;
|
||||
for (final chapter in act.chapters) {
|
||||
// 使用 sceneCount 属性,它会返回 scenes 和 sceneIds 中的较大值
|
||||
int chapterSceneCount = chapter.sceneCount;
|
||||
actSceneCount += chapterSceneCount;
|
||||
//AppLogger.d('Novel', '章节 ${chapter.id} 场景数: scenes=${chapter.scenes.length}, sceneIds=${chapter.sceneIds.length}, 取较大值=${chapterSceneCount}');
|
||||
}
|
||||
totalSceneCount += actSceneCount;
|
||||
//AppLogger.d('Novel', '卷 ${act.id} 场景总数: $actSceneCount');
|
||||
}
|
||||
|
||||
//AppLogger.d('Novel', '小说场景总数: $totalSceneCount');
|
||||
return totalSceneCount;
|
||||
}
|
||||
|
||||
/// 计算小说总章节数
|
||||
int getChapterCount() {
|
||||
int totalChapterCount = 0;
|
||||
for (final act in acts) {
|
||||
totalChapterCount += act.chapters.length;
|
||||
}
|
||||
return totalChapterCount;
|
||||
}
|
||||
|
||||
/// 计算小说总卷数
|
||||
int getActCount() {
|
||||
return acts.length;
|
||||
}
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'coverUrl': coverUrl,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'acts': acts.map((act) => act.toJson()).toList(),
|
||||
'lastEditedChapterId': lastEditedChapterId,
|
||||
'author': author?.toJson(),
|
||||
'metadata': {
|
||||
'wordCount': wordCount,
|
||||
'readTime': readTime,
|
||||
'version': version,
|
||||
'contributors': contributors,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Novel的副本
|
||||
Novel copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? coverUrl,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
List<Act>? acts,
|
||||
String? lastEditedChapterId,
|
||||
Author? author,
|
||||
int? wordCount,
|
||||
int? readTime,
|
||||
int? version,
|
||||
List<String>? contributors,
|
||||
}) {
|
||||
return Novel(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
coverUrl: coverUrl?? this.coverUrl,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
acts: acts ?? this.acts,
|
||||
lastEditedChapterId: lastEditedChapterId ?? this.lastEditedChapterId,
|
||||
author: author ?? this.author,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
readTime: readTime ?? this.readTime,
|
||||
version: version ?? this.version,
|
||||
contributors: contributors ?? this.contributors,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个空的小说结构
|
||||
static Novel createEmpty(String id, String title) {
|
||||
final now = DateTime.now();
|
||||
return Novel(
|
||||
id: id,
|
||||
title: title,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
acts: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// 添加一个新的Act
|
||||
Novel addAct(String title) {
|
||||
final newAct = Act(
|
||||
id: 'act_${DateTime.now().millisecondsSinceEpoch}',
|
||||
title: title,
|
||||
order: acts.length + 1,
|
||||
chapters: [],
|
||||
);
|
||||
|
||||
return copyWith(
|
||||
acts: [...acts, newAct],
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取指定Act
|
||||
Act? getAct(String actId) {
|
||||
try {
|
||||
return acts.firstWhere((act) => act.id == actId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定Chapter
|
||||
Chapter? getChapter(String actId, String chapterId) {
|
||||
final act = getAct(actId);
|
||||
if (act == null) return null;
|
||||
|
||||
try {
|
||||
return act.chapters.firstWhere((chapter) => chapter.id == chapterId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据章节ID直接获取章节,不需要知道Act ID
|
||||
Chapter? getChapterById(String chapterId) {
|
||||
for (final act in acts) {
|
||||
try {
|
||||
final chapter =
|
||||
act.chapters.firstWhere((chapter) => chapter.id == chapterId);
|
||||
return chapter;
|
||||
} catch (e) {
|
||||
// 继续查找下一个act
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 获取指定Scene
|
||||
Scene? getScene(String actId, String chapterId, {String? sceneId}) {
|
||||
final chapter = getChapter(actId, chapterId);
|
||||
if (chapter == null) return null;
|
||||
|
||||
if (sceneId != null) {
|
||||
// 如果提供了sceneId,则获取特定Scene
|
||||
return chapter.getScene(sceneId);
|
||||
} else if (chapter.scenes.isNotEmpty) {
|
||||
// 否则返回第一个Scene
|
||||
return chapter.scenes.first;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 获取上下文章节(前后n章)
|
||||
List<Chapter> getContextChapters(String chapterId, int n) {
|
||||
// 提取所有章节
|
||||
List<Chapter> allChapters = [];
|
||||
for (final act in acts) {
|
||||
allChapters.addAll(act.chapters);
|
||||
}
|
||||
|
||||
// 按order排序
|
||||
allChapters.sort((a, b) => a.order.compareTo(b.order));
|
||||
|
||||
// 找到当前章节的索引
|
||||
int currentIndex =
|
||||
allChapters.indexWhere((chapter) => chapter.id == chapterId);
|
||||
if (currentIndex == -1) {
|
||||
// 如果找不到当前章节,返回前n章
|
||||
return allChapters.take(n).toList();
|
||||
}
|
||||
|
||||
// 计算前后n章的范围
|
||||
int startIndex = (currentIndex - n) < 0 ? 0 : (currentIndex - n);
|
||||
int endIndex = (currentIndex + n) >= allChapters.length
|
||||
? allChapters.length - 1
|
||||
: (currentIndex + n);
|
||||
|
||||
// 提取前后n章
|
||||
return allChapters.sublist(startIndex, endIndex + 1);
|
||||
}
|
||||
|
||||
/// 更新最后编辑的章节ID
|
||||
Novel updateLastEditedChapter(String chapterId) {
|
||||
return copyWith(
|
||||
lastEditedChapterId: chapterId,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 幕模型(如Act 1, Act 2等)
|
||||
class Act {
|
||||
Act({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.order,
|
||||
this.chapters = const [],
|
||||
});
|
||||
|
||||
/// 从JSON创建Act实例
|
||||
factory Act.fromJson(Map<String, dynamic> json) {
|
||||
List<Chapter> parsedChapters = [];
|
||||
if (json['chapters'] != null && json['chapters'] is List) {
|
||||
parsedChapters = (json['chapters'] as List<dynamic>)
|
||||
.map((chapterJson) =>
|
||||
Chapter.fromJson(chapterJson as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
return Act(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
order: json['order'] as int,
|
||||
chapters: parsedChapters, // 使用解析后的列表
|
||||
);
|
||||
}
|
||||
final String id;
|
||||
final String title;
|
||||
final int order;
|
||||
final List<Chapter> chapters;
|
||||
|
||||
/// 计算Act的总字数
|
||||
int get wordCount {
|
||||
return chapters.fold(0, (sum, chapter) => sum + chapter.wordCount);
|
||||
}
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'order': order,
|
||||
'chapters': chapters.map((chapter) => chapter.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Act的副本
|
||||
Act copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
int? order,
|
||||
List<Chapter>? chapters,
|
||||
}) {
|
||||
return Act(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
order: order ?? this.order,
|
||||
chapters: chapters ?? this.chapters,
|
||||
);
|
||||
}
|
||||
|
||||
/// 添加一个新的Chapter
|
||||
Act addChapter(String title) {
|
||||
// 创建一个默认的Scene
|
||||
final defaultScene = Scene.createEmpty();
|
||||
|
||||
final newChapter = Chapter(
|
||||
id: 'chapter_${DateTime.now().millisecondsSinceEpoch}',
|
||||
title: title,
|
||||
order: chapters.length + 1,
|
||||
scenes: [defaultScene], // 包含一个默认的Scene
|
||||
);
|
||||
|
||||
return copyWith(
|
||||
chapters: [...chapters, newChapter],
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取指定Chapter
|
||||
Chapter? getChapter(String chapterId) {
|
||||
try {
|
||||
return chapters.firstWhere((chapter) => chapter.id == chapterId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 章节模型
|
||||
class Chapter {
|
||||
Chapter({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.order,
|
||||
this.scenes = const [],
|
||||
this.sceneIds = const [], // 添加 sceneIds 字段
|
||||
});
|
||||
|
||||
/// 从JSON创建Chapter实例
|
||||
factory Chapter.fromJson(Map<String, dynamic> json) {
|
||||
List<Scene> parsedScenes = [];
|
||||
List<String> parsedSceneIds = [];
|
||||
|
||||
// 解析场景列表
|
||||
if (json['scenes'] != null && json['scenes'] is List) {
|
||||
parsedScenes = (json['scenes'] as List<dynamic>)
|
||||
.map((sceneJson) => Scene.fromJson(sceneJson as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// 解析场景ID列表
|
||||
if (json['sceneIds'] != null && json['sceneIds'] is List) {
|
||||
parsedSceneIds = (json['sceneIds'] as List<dynamic>)
|
||||
.map((id) => id.toString())
|
||||
.toList();
|
||||
}
|
||||
|
||||
return Chapter(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
order: json['order'] as int,
|
||||
scenes: parsedScenes,
|
||||
sceneIds: parsedSceneIds, // 保存场景ID列表
|
||||
);
|
||||
}
|
||||
final String id;
|
||||
final String title;
|
||||
final int order;
|
||||
final List<Scene> scenes;
|
||||
final List<String> sceneIds; // 保存从后端返回的场景ID列表
|
||||
|
||||
/// 计算章节的总字数
|
||||
int get wordCount {
|
||||
return scenes.fold(0, (sum, scene) => sum + scene.wordCount);
|
||||
}
|
||||
|
||||
/// 获取场景总数(scenes列表或sceneIds列表中的较大值)
|
||||
int get sceneCount {
|
||||
int scenesLength = scenes.length;
|
||||
int sceneIdsLength = sceneIds.length;
|
||||
int result = scenesLength > sceneIdsLength ? scenesLength : sceneIdsLength;
|
||||
//AppLogger.d('Chapter', '章节 $id 场景计数: scenes=$scenesLength, sceneIds=$sceneIdsLength, 取较大值=$result');
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'order': order,
|
||||
'scenes': scenes.map((scene) => scene.toJson()).toList(),
|
||||
'sceneIds': sceneIds, // 添加场景ID列表
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Chapter的副本
|
||||
Chapter copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
int? order,
|
||||
List<Scene>? scenes,
|
||||
List<String>? sceneIds, // 添加sceneIds参数
|
||||
}) {
|
||||
return Chapter(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
order: order ?? this.order,
|
||||
scenes: scenes ?? this.scenes,
|
||||
sceneIds: sceneIds ?? this.sceneIds, // 设置sceneIds
|
||||
);
|
||||
}
|
||||
|
||||
/// 添加一个新的Scene
|
||||
void addScene(Scene newScene) {
|
||||
scenes.add(newScene);
|
||||
}
|
||||
|
||||
/// 获取指定Scene
|
||||
Scene? getScene(String sceneId) {
|
||||
try {
|
||||
return scenes.firstWhere((scene) => scene.id == sceneId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新指定Scene
|
||||
Chapter updateScene(String sceneId, Scene updatedScene) {
|
||||
final updatedScenes = scenes.map((scene) {
|
||||
if (scene.id == sceneId) {
|
||||
return updatedScene;
|
||||
}
|
||||
return scene;
|
||||
}).toList();
|
||||
|
||||
return copyWith(scenes: updatedScenes);
|
||||
}
|
||||
}
|
||||
|
||||
/// 场景模型
|
||||
class Scene {
|
||||
Scene({
|
||||
required this.id,
|
||||
required this.content,
|
||||
required this.wordCount,
|
||||
required this.summary,
|
||||
required this.lastEdited,
|
||||
this.title = '',
|
||||
this.actId = '',
|
||||
this.chapterId = '',
|
||||
this.version = 1,
|
||||
this.history = const [],
|
||||
});
|
||||
|
||||
/// 从JSON创建Scene实例
|
||||
factory Scene.fromJson(Map<String, dynamic> json) {
|
||||
// 创建安全的Summary对象
|
||||
Summary summaryObj;
|
||||
try {
|
||||
// 处理summary字段 - 可能是字符串(后端)或对象(前端)
|
||||
if (json.containsKey('summary')) {
|
||||
final summaryData = json['summary'];
|
||||
if (summaryData is Map<String, dynamic>) {
|
||||
// 如果是对象格式,直接解析
|
||||
summaryObj = Summary.fromJson(summaryData);
|
||||
} else if (summaryData is String) {
|
||||
// 如果是字符串格式(后端发送的),创建Summary对象
|
||||
final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
summaryObj = Summary(
|
||||
id: '${sceneId}_summary',
|
||||
content: summaryData,
|
||||
);
|
||||
} else {
|
||||
// 其他格式,创建默认Summary
|
||||
final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
summaryObj = Summary(
|
||||
id: '${sceneId}_summary',
|
||||
content: '',
|
||||
);
|
||||
AppLogger.w('Scene.fromJson', '场景 $sceneId 的摘要字段类型不支持: ${summaryData.runtimeType}');
|
||||
}
|
||||
} else {
|
||||
// 创建默认Summary
|
||||
final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
summaryObj = Summary(
|
||||
id: '${sceneId}_summary',
|
||||
content: '',
|
||||
);
|
||||
AppLogger.w('Scene.fromJson', '场景 $sceneId 缺少摘要字段,已创建默认摘要');
|
||||
}
|
||||
} catch (e) {
|
||||
// 处理任何异常,创建默认Summary
|
||||
final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
summaryObj = Summary(
|
||||
id: '${sceneId}_summary',
|
||||
content: '',
|
||||
);
|
||||
AppLogger.e('Scene.fromJson', '解析场景 $sceneId 的摘要时出错', e);
|
||||
}
|
||||
|
||||
// 安全解析lastEdited字段,支持多种日期格式
|
||||
DateTime lastEditedDate;
|
||||
try {
|
||||
if (json.containsKey('lastEdited') && json['lastEdited'] != null) {
|
||||
final lastEditedStr = json['lastEdited'].toString();
|
||||
lastEditedDate = _parseDateTime(lastEditedStr);
|
||||
} else if (json.containsKey('updatedAt') && json['updatedAt'] != null) {
|
||||
// 兼容后端可能使用updatedAt字段
|
||||
final updatedAtStr = json['updatedAt'].toString();
|
||||
lastEditedDate = _parseDateTime(updatedAtStr);
|
||||
} else {
|
||||
lastEditedDate = DateTime.now();
|
||||
AppLogger.w('Scene.fromJson', '场景 ${json['id']} 缺少时间字段,使用当前时间');
|
||||
}
|
||||
} catch (e) {
|
||||
lastEditedDate = DateTime.now();
|
||||
AppLogger.w('Scene.fromJson', '解析场景 ${json['id']} 的时间字段失败,使用当前时间', e);
|
||||
}
|
||||
|
||||
return Scene(
|
||||
id: json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
content: json['content'] ?? '',
|
||||
wordCount: json['wordCount'] ?? 0,
|
||||
summary: summaryObj,
|
||||
lastEdited: lastEditedDate,
|
||||
title: json['title'] ?? '',
|
||||
actId: json['actId'] ?? '',
|
||||
chapterId: json['chapterId'] ?? '',
|
||||
version: json['version'] ?? 1,
|
||||
history: [],
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String content;
|
||||
final int wordCount;
|
||||
final Summary summary;
|
||||
final DateTime lastEdited;
|
||||
final String title;
|
||||
final String actId;
|
||||
final String chapterId;
|
||||
final int version;
|
||||
final List<HistoryEntry> history;
|
||||
|
||||
/// 解析多种日期格式的工具方法
|
||||
static DateTime _parseDateTime(String dateTimeStr) {
|
||||
if (dateTimeStr.isEmpty) {
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试标准ISO格式
|
||||
return DateTime.parse(dateTimeStr);
|
||||
} catch (e1) {
|
||||
try {
|
||||
// 尝试处理带毫秒的格式 "yyyy-MM-dd'T'HH:mm:ss.SSS"
|
||||
if (dateTimeStr.contains('T') && dateTimeStr.contains('.')) {
|
||||
// 如果包含时区信息,先移除
|
||||
String cleanStr = dateTimeStr;
|
||||
if (cleanStr.endsWith('Z')) {
|
||||
cleanStr = cleanStr.substring(0, cleanStr.length - 1);
|
||||
}
|
||||
if (cleanStr.contains('+') || cleanStr.lastIndexOf('-') > 10) {
|
||||
// 移除时区偏移
|
||||
final timeZoneIndex = cleanStr.lastIndexOf('+') > cleanStr.lastIndexOf('-')
|
||||
? cleanStr.lastIndexOf('+')
|
||||
: cleanStr.lastIndexOf('-');
|
||||
if (timeZoneIndex > 10) {
|
||||
cleanStr = cleanStr.substring(0, timeZoneIndex);
|
||||
}
|
||||
}
|
||||
return DateTime.parse(cleanStr);
|
||||
}
|
||||
|
||||
// 尝试其他常见格式
|
||||
// 格式:yyyy-MM-dd HH:mm:ss
|
||||
if (dateTimeStr.contains(' ') && !dateTimeStr.contains('T')) {
|
||||
final parts = dateTimeStr.split(' ');
|
||||
if (parts.length == 2) {
|
||||
final datePart = parts[0];
|
||||
final timePart = parts[1];
|
||||
final isoStr = '${datePart}T$timePart';
|
||||
return DateTime.parse(isoStr);
|
||||
}
|
||||
}
|
||||
|
||||
throw e1; // 如果都失败了,抛出原始异常
|
||||
} catch (e2) {
|
||||
AppLogger.w('Scene._parseDateTime', '无法解析日期格式: $dateTimeStr,使用当前时间');
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'content': content,
|
||||
'wordCount': wordCount,
|
||||
'summary': summary.toJson(),
|
||||
'lastEdited': lastEdited.toIso8601String(),
|
||||
'title': title,
|
||||
'actId': actId,
|
||||
'chapterId': chapterId,
|
||||
'version': version,
|
||||
'history': history.map((entry) => entry.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Scene的副本
|
||||
Scene copyWith({
|
||||
String? id,
|
||||
String? content,
|
||||
int? wordCount,
|
||||
Summary? summary,
|
||||
DateTime? lastEdited,
|
||||
String? title,
|
||||
String? actId,
|
||||
String? chapterId,
|
||||
int? version,
|
||||
List<HistoryEntry>? history,
|
||||
}) {
|
||||
return Scene(
|
||||
id: id ?? this.id,
|
||||
content: content ?? this.content,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
summary: summary ?? this.summary,
|
||||
lastEdited: lastEdited ?? this.lastEdited,
|
||||
title: title ?? this.title,
|
||||
actId: actId ?? this.actId,
|
||||
chapterId: chapterId ?? this.chapterId,
|
||||
version: version ?? this.version,
|
||||
history: history ?? this.history,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个空的场景
|
||||
static Scene createEmpty() {
|
||||
const defaultContent = '{"ops":[{"insert":"\\n"}]}'; // <-- 确保是这个值
|
||||
final now = DateTime.now();
|
||||
return Scene(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
content: defaultContent,
|
||||
wordCount: 0,
|
||||
summary: Summary(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}_summary',
|
||||
content: '',
|
||||
),
|
||||
lastEdited: now,
|
||||
title: '',
|
||||
actId: '',
|
||||
chapterId: '',
|
||||
version: 1,
|
||||
history: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个默认的场景
|
||||
static Scene createDefault(String sceneIdBase) {
|
||||
// 使用正确Quill Delta格式包含ops对象的内容
|
||||
const defaultContent = '{"ops":[{"insert":"\\n"}]}';
|
||||
final now = DateTime.now();
|
||||
return Scene(
|
||||
id: sceneIdBase,
|
||||
content: defaultContent,
|
||||
wordCount: 0,
|
||||
summary: Summary(
|
||||
id: '${sceneIdBase}_summary',
|
||||
content: '',
|
||||
),
|
||||
lastEdited: now,
|
||||
title: '新场景',
|
||||
actId: '',
|
||||
chapterId: '',
|
||||
version: 1,
|
||||
history: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 摘要模型
|
||||
class Summary {
|
||||
Summary({
|
||||
required this.id,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
/// 从JSON创建Summary实例
|
||||
factory Summary.fromJson(Map<String, dynamic> json) {
|
||||
return Summary(
|
||||
id: json['id'] as String,
|
||||
content: json['content'] as String,
|
||||
);
|
||||
}
|
||||
final String id;
|
||||
final String content;
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'content': content,
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Summary的副本
|
||||
Summary copyWith({
|
||||
String? id,
|
||||
String? content,
|
||||
}) {
|
||||
return Summary(
|
||||
id: id ?? this.id,
|
||||
content: content ?? this.content,
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个空的摘要
|
||||
static Summary createEmpty() {
|
||||
return Summary(
|
||||
id: 'summary_${DateTime.now().millisecondsSinceEpoch}',
|
||||
content: '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryEntry {
|
||||
HistoryEntry({
|
||||
this.content,
|
||||
required this.updatedAt,
|
||||
required this.updatedBy,
|
||||
required this.reason,
|
||||
});
|
||||
|
||||
factory HistoryEntry.fromJson(Map<String, dynamic> json) {
|
||||
DateTime updatedAt;
|
||||
try {
|
||||
updatedAt = DateTime.parse(json['updatedAt']);
|
||||
} catch (e) {
|
||||
updatedAt = DateTime.now();
|
||||
}
|
||||
|
||||
return HistoryEntry(
|
||||
content: json['content'],
|
||||
updatedAt: updatedAt,
|
||||
updatedBy: json['updatedBy'] ?? 'unknown',
|
||||
reason: json['reason'] ?? '',
|
||||
);
|
||||
}
|
||||
final String? content;
|
||||
final DateTime updatedAt;
|
||||
final String updatedBy;
|
||||
final String reason;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'content': content,
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'updatedBy': updatedBy,
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
/// 作者信息模型
|
||||
class Author {
|
||||
Author({
|
||||
required this.id,
|
||||
required this.username,
|
||||
});
|
||||
|
||||
/// 从JSON创建Author实例
|
||||
factory Author.fromJson(Map<String, dynamic> json) {
|
||||
return Author(
|
||||
id: json['id'] ?? '',
|
||||
username: json['username'] ?? '未知作者',
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String username;
|
||||
|
||||
/// 转换为JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'username': username,
|
||||
};
|
||||
}
|
||||
|
||||
/// 创建Author的副本
|
||||
Author copyWith({
|
||||
String? id,
|
||||
String? username,
|
||||
}) {
|
||||
return Author(
|
||||
id: id ?? this.id,
|
||||
username: username ?? this.username,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user