Files
MaliangAINovalWriter/AINoval/lib/models/novel_structure.dart
2025-09-10 00:07:52 +08:00

951 lines
27 KiB
Dart
Raw Permalink 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.

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,
);
}
}