f6929ae18f
Co-Authored-By: Claude Code <noreply@anthropic.com>
650 lines
20 KiB
Dart
650 lines
20 KiB
Dart
/// 相册域模型(P3 计划 Task 2):相册、分类、成员、账号查询与请求体。
|
||
///
|
||
/// 契约来源:`packages/contracts/openapi.yaml` tag `albums`/`categories`/
|
||
/// `members`/`admin`。
|
||
library;
|
||
|
||
/// 相册多语言条目(创建/更新请求与响应共用)。
|
||
///
|
||
/// 契约 `components.schemas.AlbumTranslation`:未填的可选字段存储为 null
|
||
/// (回读时 null 而非缺省键)。
|
||
class AlbumTranslation {
|
||
const AlbumTranslation({
|
||
required this.locale,
|
||
required this.title,
|
||
this.description,
|
||
this.venue,
|
||
this.address,
|
||
});
|
||
|
||
factory AlbumTranslation.fromJson(Map<String, dynamic> json) =>
|
||
AlbumTranslation(
|
||
locale: json['locale'] as String,
|
||
title: json['title'] as String,
|
||
description: json['description'] as String?,
|
||
venue: json['venue'] as String?,
|
||
address: json['address'] as String?,
|
||
);
|
||
|
||
/// 语言(契约 Locale enum:zh-CN | en;Dart 标识符不兼容连字符,
|
||
/// 按 String 透传)。
|
||
final String locale;
|
||
|
||
/// 相册标题。
|
||
final String title;
|
||
|
||
/// 相册描述;未填为 null。
|
||
final String? description;
|
||
|
||
/// 场地名称;未填为 null。
|
||
final String? venue;
|
||
|
||
/// 场地地址;未填为 null。
|
||
final String? address;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'locale': locale,
|
||
'title': title,
|
||
'description': description,
|
||
'venue': venue,
|
||
'address': address,
|
||
};
|
||
}
|
||
|
||
/// 相册状态。
|
||
///
|
||
/// 契约 `AlbumSummary.status` enum:active=正常;offline=下线
|
||
/// (停止公开访问与新授权签发)。
|
||
enum AlbumStatus {
|
||
active('active'),
|
||
offline('offline');
|
||
|
||
const AlbumStatus(this.wire);
|
||
|
||
/// 线上(JSON)取值。
|
||
final String wire;
|
||
|
||
static AlbumStatus fromJson(String value) => values.firstWhere(
|
||
(v) => v.wire == value,
|
||
orElse: () => throw FormatException('未知 AlbumStatus: $value'),
|
||
);
|
||
}
|
||
|
||
/// 私有对象的短期签名 URL。
|
||
///
|
||
/// 契约 `components.schemas.SignedUrl`(UI-01 起相册封面/照片预览共用):
|
||
/// 不得写入持久分享链接,过期后经原入口(列表/详情重取)刷新。
|
||
class SignedUrl {
|
||
const SignedUrl({required this.url, required this.expiresAt});
|
||
|
||
factory SignedUrl.fromJson(Map<String, dynamic> json) => SignedUrl(
|
||
url: json['url'] as String,
|
||
expiresAt: DateTime.parse(json['expiresAt'] as String),
|
||
);
|
||
|
||
/// 签名访问 URL。
|
||
final String url;
|
||
|
||
/// URL 失效时间。
|
||
final DateTime expiresAt;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'url': url,
|
||
'expiresAt': expiresAt.toUtc().toIso8601String(),
|
||
};
|
||
}
|
||
|
||
/// 相册列表条目(完整开关与多语言标题,管理列表无需二次请求详情)。
|
||
///
|
||
/// 契约 `components.schemas.AlbumSummary`(GET /albums,
|
||
/// operationId `listAlbums` 的条目)。cover(UI-01 可选扩展):当前生效
|
||
/// 封面的短期签名 URL;旧响应缺键或未配置封面为 null。
|
||
class AlbumSummary {
|
||
const AlbumSummary({
|
||
required this.id,
|
||
required this.publicToken,
|
||
required this.ownerId,
|
||
required this.status,
|
||
required this.defaultLocale,
|
||
required this.timezone,
|
||
required this.startAt,
|
||
required this.endAt,
|
||
required this.uploadEnabled,
|
||
required this.publicEnabled,
|
||
required this.downloadEnabled,
|
||
required this.originalDownloadEnabled,
|
||
required this.watermarkVersion,
|
||
required this.translations,
|
||
required this.createdAt,
|
||
required this.updatedAt,
|
||
required this.statsSyncedAt,
|
||
this.cover,
|
||
});
|
||
|
||
factory AlbumSummary.fromJson(Map<String, dynamic> json) => AlbumSummary(
|
||
id: json['id'] as String,
|
||
publicToken: json['publicToken'] as String,
|
||
ownerId: json['ownerId'] as String,
|
||
status: AlbumStatus.fromJson(json['status'] as String),
|
||
defaultLocale: json['defaultLocale'] as String,
|
||
timezone: json['timezone'] as String,
|
||
startAt: DateTime.parse(json['startAt'] as String),
|
||
endAt: json['endAt'] == null
|
||
? null
|
||
: DateTime.parse(json['endAt'] as String),
|
||
uploadEnabled: json['uploadEnabled'] as bool,
|
||
publicEnabled: json['publicEnabled'] as bool,
|
||
downloadEnabled: json['downloadEnabled'] as bool,
|
||
originalDownloadEnabled: json['originalDownloadEnabled'] as bool,
|
||
watermarkVersion: json['watermarkVersion'] as int,
|
||
translations: (json['translations'] as List<dynamic>)
|
||
.map(
|
||
(dynamic e) => AlbumTranslation.fromJson(
|
||
e as Map<String, dynamic>,
|
||
),
|
||
)
|
||
.toList(),
|
||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||
statsSyncedAt: json['statsSyncedAt'] == null
|
||
? null
|
||
: DateTime.parse(json['statsSyncedAt'] as String),
|
||
cover: json['cover'] == null
|
||
? null
|
||
: SignedUrl.fromJson(json['cover'] as Map<String, dynamic>),
|
||
);
|
||
|
||
/// 相册 ID。
|
||
final String id;
|
||
|
||
/// 公开访问令牌(不可枚举,pattern ^[0-9a-z]{20,32}$)。
|
||
final String publicToken;
|
||
|
||
/// 创建者用户 ID。
|
||
final String ownerId;
|
||
|
||
/// 相册状态。
|
||
final AlbumStatus status;
|
||
|
||
/// 缺省语言(契约 Locale enum:zh-CN | en)。
|
||
final String defaultLocale;
|
||
|
||
/// 活动 IANA 时区。
|
||
final String timezone;
|
||
|
||
/// 活动开始时间(UTC 存储,展示按 timezone)。
|
||
final DateTime startAt;
|
||
|
||
/// 活动结束时间;null 表示未定。
|
||
final DateTime? endAt;
|
||
|
||
/// 是否允许新上传任务。
|
||
final bool uploadEnabled;
|
||
|
||
/// 是否公开可见。
|
||
final bool publicEnabled;
|
||
|
||
/// 允许下载浏览版本。
|
||
final bool downloadEnabled;
|
||
|
||
/// 允许下载原图。
|
||
final bool originalDownloadEnabled;
|
||
|
||
/// 当前水印配置版本。
|
||
final int watermarkVersion;
|
||
|
||
/// 各语言内容。
|
||
final List<AlbumTranslation> translations;
|
||
|
||
/// 创建时间。
|
||
final DateTime createdAt;
|
||
|
||
/// 最近更新时间。
|
||
final DateTime updatedAt;
|
||
|
||
/// 统计副本最近同步时间(P2 A27);null 表示尚无回传应用。
|
||
final DateTime? statsSyncedAt;
|
||
|
||
/// 相册封面签名 URL(UI-01);未配置封面或旧响应缺键为 null。
|
||
final SignedUrl? cover;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'publicToken': publicToken,
|
||
'ownerId': ownerId,
|
||
'status': status.wire,
|
||
'defaultLocale': defaultLocale,
|
||
'timezone': timezone,
|
||
'startAt': startAt.toUtc().toIso8601String(),
|
||
'endAt': endAt?.toUtc().toIso8601String(),
|
||
'uploadEnabled': uploadEnabled,
|
||
'publicEnabled': publicEnabled,
|
||
'downloadEnabled': downloadEnabled,
|
||
'originalDownloadEnabled': originalDownloadEnabled,
|
||
'watermarkVersion': watermarkVersion,
|
||
'translations': translations
|
||
.map((AlbumTranslation t) => t.toJson())
|
||
.toList(),
|
||
'createdAt': createdAt.toUtc().toIso8601String(),
|
||
'updatedAt': updatedAt.toUtc().toIso8601String(),
|
||
'statsSyncedAt': statsSyncedAt?.toUtc().toIso8601String(),
|
||
'cover': cover?.toJson(),
|
||
};
|
||
}
|
||
|
||
/// 相册详情。
|
||
///
|
||
/// 契约 `components.schemas.Album`(= AlbumSummary + categories,按
|
||
/// sortOrder 升序);POST /albums、GET/PATCH /albums/{albumId}
|
||
/// (operationId `createAlbum` / `getAlbum` / `updateAlbum`)的响应。
|
||
class Album extends AlbumSummary {
|
||
const Album({
|
||
required this.categories,
|
||
required super.id,
|
||
required super.publicToken,
|
||
required super.ownerId,
|
||
required super.status,
|
||
required super.defaultLocale,
|
||
required super.timezone,
|
||
required super.startAt,
|
||
required super.endAt,
|
||
required super.uploadEnabled,
|
||
required super.publicEnabled,
|
||
required super.downloadEnabled,
|
||
required super.originalDownloadEnabled,
|
||
required super.watermarkVersion,
|
||
required super.translations,
|
||
required super.createdAt,
|
||
required super.updatedAt,
|
||
required super.statsSyncedAt,
|
||
super.cover,
|
||
});
|
||
|
||
factory Album.fromJson(Map<String, dynamic> json) => Album(
|
||
categories: (json['categories'] as List<dynamic>)
|
||
.map(
|
||
(dynamic e) => Category.fromJson(e as Map<String, dynamic>),
|
||
)
|
||
.toList(),
|
||
cover: json['cover'] == null
|
||
? null
|
||
: SignedUrl.fromJson(json['cover'] as Map<String, dynamic>),
|
||
id: json['id'] as String,
|
||
publicToken: json['publicToken'] as String,
|
||
ownerId: json['ownerId'] as String,
|
||
status: AlbumStatus.fromJson(json['status'] as String),
|
||
defaultLocale: json['defaultLocale'] as String,
|
||
timezone: json['timezone'] as String,
|
||
startAt: DateTime.parse(json['startAt'] as String),
|
||
endAt: json['endAt'] == null
|
||
? null
|
||
: DateTime.parse(json['endAt'] as String),
|
||
uploadEnabled: json['uploadEnabled'] as bool,
|
||
publicEnabled: json['publicEnabled'] as bool,
|
||
downloadEnabled: json['downloadEnabled'] as bool,
|
||
originalDownloadEnabled: json['originalDownloadEnabled'] as bool,
|
||
watermarkVersion: json['watermarkVersion'] as int,
|
||
translations: (json['translations'] as List<dynamic>)
|
||
.map(
|
||
(dynamic e) => AlbumTranslation.fromJson(
|
||
e as Map<String, dynamic>,
|
||
),
|
||
)
|
||
.toList(),
|
||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||
statsSyncedAt: json['statsSyncedAt'] == null
|
||
? null
|
||
: DateTime.parse(json['statsSyncedAt'] as String),
|
||
);
|
||
|
||
/// 相册分类(按 sortOrder 升序;新相册为空数组)。
|
||
final List<Category> categories;
|
||
|
||
@override
|
||
Map<String, dynamic> toJson() =>
|
||
{...super.toJson(), 'categories': categories.map((c) => c.toJson()).toList()};
|
||
}
|
||
|
||
/// 分类多语言名称条目。
|
||
///
|
||
/// 契约 `components.schemas.CategoryTranslation`。
|
||
class CategoryTranslation {
|
||
const CategoryTranslation({required this.locale, required this.name});
|
||
|
||
factory CategoryTranslation.fromJson(Map<String, dynamic> json) =>
|
||
CategoryTranslation(
|
||
locale: json['locale'] as String,
|
||
name: json['name'] as String,
|
||
);
|
||
|
||
/// 语言(契约 Locale enum:zh-CN | en)。
|
||
final String locale;
|
||
|
||
/// 分类名称。
|
||
final String name;
|
||
|
||
Map<String, dynamic> toJson() => {'locale': locale, 'name': name};
|
||
}
|
||
|
||
/// 相册分类。
|
||
///
|
||
/// 契约 `components.schemas.Category`;POST /albums/{albumId}/categories、
|
||
/// PATCH /categories/{categoryId}(operationId `createCategory` /
|
||
/// `updateCategory`)的响应,亦内嵌于 [Album.categories]。
|
||
class Category {
|
||
const Category({
|
||
required this.id,
|
||
required this.albumId,
|
||
required this.sortOrder,
|
||
required this.translations,
|
||
});
|
||
|
||
factory Category.fromJson(Map<String, dynamic> json) => Category(
|
||
id: json['id'] as String,
|
||
albumId: json['albumId'] as String,
|
||
sortOrder: json['sortOrder'] as int,
|
||
translations: (json['translations'] as List<dynamic>)
|
||
.map(
|
||
(dynamic e) =>
|
||
CategoryTranslation.fromJson(e as Map<String, dynamic>),
|
||
)
|
||
.toList(),
|
||
);
|
||
|
||
/// 分类 ID。
|
||
final String id;
|
||
|
||
/// 所属相册 ID。
|
||
final String albumId;
|
||
|
||
/// 排序序号(升序)。
|
||
final int sortOrder;
|
||
|
||
/// 各语言名称。
|
||
final List<CategoryTranslation> translations;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'albumId': albumId,
|
||
'sortOrder': sortOrder,
|
||
'translations':
|
||
translations.map((CategoryTranslation t) => t.toJson()).toList(),
|
||
};
|
||
}
|
||
|
||
/// 成员角色。
|
||
///
|
||
/// 契约 `Member.role` enum:owner 由创建流程产生,P1a 仅 collaborator 可添加。
|
||
enum MemberRole {
|
||
owner('owner'),
|
||
collaborator('collaborator');
|
||
|
||
const MemberRole(this.wire);
|
||
|
||
/// 线上(JSON)取值。
|
||
final String wire;
|
||
|
||
static MemberRole fromJson(String value) => values.firstWhere(
|
||
(v) => v.wire == value,
|
||
orElse: () => throw FormatException('未知 MemberRole: $value'),
|
||
);
|
||
}
|
||
|
||
/// 成员状态。
|
||
///
|
||
/// 契约 `Member.status` enum:P1a 仅 active,预留扩展。
|
||
enum MemberStatus {
|
||
active('active');
|
||
|
||
const MemberStatus(this.wire);
|
||
|
||
/// 线上(JSON)取值。
|
||
final String wire;
|
||
|
||
static MemberStatus fromJson(String value) => values.firstWhere(
|
||
(v) => v.wire == value,
|
||
orElse: () => throw FormatException('未知 MemberStatus: $value'),
|
||
);
|
||
}
|
||
|
||
/// 相册成员。
|
||
///
|
||
/// 契约 `components.schemas.Member`;POST /albums/{albumId}/members
|
||
/// (operationId `addAlbumMember`)的响应、GET 同路径
|
||
/// (`listAlbumMembers`)的条目。
|
||
class Member {
|
||
const Member({
|
||
required this.userId,
|
||
required this.account,
|
||
required this.displayName,
|
||
required this.role,
|
||
required this.status,
|
||
});
|
||
|
||
factory Member.fromJson(Map<String, dynamic> json) => Member(
|
||
userId: json['userId'] as String,
|
||
account: json['account'] as String,
|
||
displayName: json['displayName'] as String,
|
||
role: MemberRole.fromJson(json['role'] as String),
|
||
status: MemberStatus.fromJson(json['status'] as String),
|
||
);
|
||
|
||
/// 用户 ID。
|
||
final String userId;
|
||
|
||
/// 登录账号。
|
||
final String account;
|
||
|
||
/// 展示姓名(来自用户资料同步)。
|
||
final String displayName;
|
||
|
||
/// 相册角色。
|
||
final MemberRole role;
|
||
|
||
/// 成员状态。
|
||
final MemberStatus status;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'userId': userId,
|
||
'account': account,
|
||
'displayName': displayName,
|
||
'role': role.wire,
|
||
'status': status.wire,
|
||
};
|
||
}
|
||
|
||
/// 账号前缀查询条目(脱敏)。
|
||
///
|
||
/// 契约 `components.schemas.AdminUserLookupItem`;GET /admin/users
|
||
/// (operationId `lookupUsersByAccount`)的条目,用于邀请协作者的
|
||
/// 前置搜索。无游标(前缀搜索 + 小上限即取即用)。
|
||
class AdminUserLookupItem {
|
||
const AdminUserLookupItem({
|
||
required this.id,
|
||
required this.account,
|
||
required this.displayName,
|
||
});
|
||
|
||
factory AdminUserLookupItem.fromJson(Map<String, dynamic> json) =>
|
||
AdminUserLookupItem(
|
||
id: json['id'] as String,
|
||
account: json['account'] as String,
|
||
displayName: json['displayName'] as String,
|
||
);
|
||
|
||
/// 用户 ID(用于 POST /albums/{albumId}/members 的 userId)。
|
||
final String id;
|
||
|
||
/// 登录账号。
|
||
final String account;
|
||
|
||
/// 展示姓名。
|
||
final String displayName;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'account': account,
|
||
'displayName': displayName,
|
||
};
|
||
}
|
||
|
||
/// 创建相册请求体。
|
||
///
|
||
/// 契约 `components.schemas.AlbumCreateRequest`(operationId
|
||
/// `createAlbum`)。初始默认值(服务端):uploadEnabled/publicEnabled=
|
||
/// downloadEnabled=true、originalDownloadEnabled=false、status=active、
|
||
/// watermarkVersion=0——故请求体不含 uploadEnabled/publicEnabled/status。
|
||
class AlbumCreateRequest {
|
||
const AlbumCreateRequest({
|
||
required this.translations,
|
||
required this.defaultLocale,
|
||
required this.startAt,
|
||
this.endAt,
|
||
this.timezone = 'Asia/Shanghai',
|
||
this.downloadEnabled = true,
|
||
this.originalDownloadEnabled = false,
|
||
});
|
||
|
||
/// 多语言内容;必须包含 defaultLocale 对应条目(minItems 1)。
|
||
final List<AlbumTranslation> translations;
|
||
|
||
/// 缺省语言(契约 Locale enum:zh-CN | en)。
|
||
final String defaultLocale;
|
||
|
||
/// 活动开始时间。
|
||
final DateTime startAt;
|
||
|
||
/// 活动结束时间;null/缺省表示未定。
|
||
final DateTime? endAt;
|
||
|
||
/// 活动 IANA 时区(契约缺省 Asia/Shanghai)。
|
||
final String timezone;
|
||
|
||
/// 允许下载浏览版本(契约缺省 true)。
|
||
final bool downloadEnabled;
|
||
|
||
/// 允许下载原图(契约缺省 false)。
|
||
final bool originalDownloadEnabled;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'translations':
|
||
translations.map((AlbumTranslation t) => t.toJson()).toList(),
|
||
'defaultLocale': defaultLocale,
|
||
'startAt': startAt.toUtc().toIso8601String(),
|
||
'endAt': endAt?.toUtc().toIso8601String(),
|
||
'timezone': timezone,
|
||
'downloadEnabled': downloadEnabled,
|
||
'originalDownloadEnabled': originalDownloadEnabled,
|
||
};
|
||
}
|
||
|
||
/// 更新相册请求体(部分更新:仅提交的字段被修改)。
|
||
///
|
||
/// 契约 `components.schemas.AlbumUpdateRequest`(operationId
|
||
/// `updateAlbum`,minProperties 1)。translations 为全量替换(须包含
|
||
/// defaultLocale 对应条目);[endAt] 传 null 清除——「未提供」与
|
||
/// 「显式清除」经 [clearEndAt] 区分(Dart 可空字段无法自带该语义)。
|
||
class AlbumUpdateRequest {
|
||
const AlbumUpdateRequest({
|
||
this.translations,
|
||
this.defaultLocale,
|
||
this.startAt,
|
||
this.endAt,
|
||
this.clearEndAt = false,
|
||
this.timezone,
|
||
this.uploadEnabled,
|
||
this.publicEnabled,
|
||
this.downloadEnabled,
|
||
this.originalDownloadEnabled,
|
||
});
|
||
|
||
/// 全量替换多语言内容(须包含 defaultLocale 对应条目)。
|
||
final List<AlbumTranslation>? translations;
|
||
|
||
/// 缺省语言。
|
||
final String? defaultLocale;
|
||
|
||
/// 活动开始时间。
|
||
final DateTime? startAt;
|
||
|
||
/// 活动结束时间;与 [clearEndAt] 互斥使用。
|
||
final DateTime? endAt;
|
||
|
||
/// 显式清除 endAt(序列化为 `"endAt": null`)。
|
||
final bool clearEndAt;
|
||
|
||
/// 活动 IANA 时区。
|
||
final String? timezone;
|
||
|
||
/// 是否允许新上传任务。
|
||
final bool? uploadEnabled;
|
||
|
||
/// 是否公开可见。
|
||
final bool? publicEnabled;
|
||
|
||
/// 允许下载浏览版本。
|
||
final bool? downloadEnabled;
|
||
|
||
/// 允许下载原图。
|
||
final bool? originalDownloadEnabled;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
if (translations != null)
|
||
'translations':
|
||
translations!.map((AlbumTranslation t) => t.toJson()).toList(),
|
||
if (defaultLocale != null) 'defaultLocale': defaultLocale,
|
||
if (startAt != null) 'startAt': startAt!.toUtc().toIso8601String(),
|
||
if (endAt != null) 'endAt': endAt!.toUtc().toIso8601String(),
|
||
if (clearEndAt) 'endAt': null,
|
||
if (timezone != null) 'timezone': timezone,
|
||
if (uploadEnabled != null) 'uploadEnabled': uploadEnabled,
|
||
if (publicEnabled != null) 'publicEnabled': publicEnabled,
|
||
if (downloadEnabled != null) 'downloadEnabled': downloadEnabled,
|
||
if (originalDownloadEnabled != null)
|
||
'originalDownloadEnabled': originalDownloadEnabled,
|
||
};
|
||
}
|
||
|
||
/// 创建分类请求体。
|
||
///
|
||
/// 契约 `components.schemas.CategoryCreateRequest`(operationId
|
||
/// `createCategory`)。sortOrder 契约缺省 0——null 表示不提交(走服务端
|
||
/// 缺省)。
|
||
class CategoryCreateRequest {
|
||
const CategoryCreateRequest({this.sortOrder, required this.translations});
|
||
|
||
/// 排序序号(升序);null 表示不提交(服务端缺省 0)。
|
||
final int? sortOrder;
|
||
|
||
/// 分类多语言名称(minItems 1)。
|
||
final List<CategoryTranslation> translations;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
if (sortOrder != null) 'sortOrder': sortOrder,
|
||
'translations':
|
||
translations.map((CategoryTranslation t) => t.toJson()).toList(),
|
||
};
|
||
}
|
||
|
||
/// 更新分类请求体(部分更新;translations 为全量替换)。
|
||
///
|
||
/// 契约 `components.schemas.CategoryUpdateRequest`(operationId
|
||
/// `updateCategory`,minProperties 1)。
|
||
class CategoryUpdateRequest {
|
||
const CategoryUpdateRequest({this.sortOrder, this.translations});
|
||
|
||
/// 排序序号(升序)。
|
||
final int? sortOrder;
|
||
|
||
/// 全量替换多语言名称。
|
||
final List<CategoryTranslation>? translations;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
if (sortOrder != null) 'sortOrder': sortOrder,
|
||
if (translations != null)
|
||
'translations':
|
||
translations!.map((CategoryTranslation t) => t.toJson()).toList(),
|
||
};
|
||
}
|