/// 滚动字幕消息模型。 /// /// 对应 Web 后台「滚动字幕」表(tv_scrolling_text / theme_scrolling_text) /// 以及管理模块「滚动字幕管理」中的单条字幕记录。 /// /// 字段映射: /// - Web 端返回 `orgId` / `orgName`,本模型内部使用 `storeId` / `storeName`。 /// - Web 端返回 `scrollSpeed`,本模型内部使用 `speed`。 /// - Web 端返回 `bgColor`,本模型内部使用 `backgroundColor`。 /// - `status` 沿用 Web 端语义:`1=启用`,`0=禁用`。 class MarqueeMessage { const MarqueeMessage({ required this.id, required this.storeId, required this.storeName, required this.content, required this.status, required this.speed, required this.createTime, required this.updateTime, this.fontColor, this.backgroundColor, this.fontSize, this.level, this.startTime, this.endTime, }); /// 字幕 ID。 final String id; /// 所属门店 ID(对应 Web 端 orgId)。 final String storeId; /// 所属门店名称(对应 Web 端 orgName)。 final String storeName; /// 字幕内容。 final String content; /// 状态:`1=启用`,`0=禁用`。 final int status; /// 滚动速度(对应 Web 端 scrollSpeed,单位 ms/像素,越小越快)。 /// /// 档位映射:慢=200 / 较慢=100 / 中=50 / 较快=30 / 快=10。 final int speed; /// 字体颜色(Hex,如 `#FFFFFF`)。 final String? fontColor; /// 背景颜色(Hex,如 `#3B82F6`,对应 Web 端 bgColor)。 final String? backgroundColor; /// 字体大小(px,如 32)。 final int? fontSize; /// 字幕级别/优先级。 final int? level; /// 开始生效时间(Web 端为 `HH:mm:ss` 字符串,本地存为时间)。 final DateTime? startTime; /// 结束生效时间(Web 端为 `HH:mm:ss` 字符串,本地存为时间)。 final DateTime? endTime; /// 创建时间。 final DateTime? createTime; /// 更新时间。 final DateTime? updateTime; bool get isEnabled => status == 1; bool get isDisabled => status == 0; factory MarqueeMessage.fromJson(Map json) { return MarqueeMessage( id: (json['id'] ?? json['marqueeId'])?.toString() ?? '', storeId: json['storeId']?.toString() ?? json['orgId']?.toString() ?? json['org_id']?.toString() ?? '', storeName: json['storeName']?.toString() ?? json['orgName']?.toString() ?? json['org_name']?.toString() ?? '', content: json['content']?.toString() ?? '', status: _parseInt(json['status'], fallback: 0) ?? 0, speed: _parseInt(json['speed'], fallback: 50) ?? _parseInt(json['scrollSpeed'], fallback: 50) ?? _parseInt(json['scroll_speed'], fallback: 50) ?? 50, fontColor: json['fontColor']?.toString() ?? json['font_color']?.toString(), backgroundColor: json['backgroundColor']?.toString() ?? json['bgColor']?.toString() ?? json['bg_color']?.toString(), fontSize: _parseInt(json['fontSize'] ?? json['font_size']), level: _parseInt(json['level']), startTime: _parseDateTime(json['startTime'] ?? json['start_time']), endTime: _parseDateTime(json['endTime'] ?? json['end_time']), createTime: _parseDateTime(json['createTime'] ?? json['create_time']), updateTime: _parseDateTime(json['updateTime'] ?? json['update_time']), ); } /// 转换为本地缓存使用的 JSON。 Map toJson() { return { 'id': id, 'storeId': storeId, 'storeName': storeName, 'content': content, 'status': status, 'speed': speed, if (fontColor != null) 'fontColor': fontColor, if (backgroundColor != null) 'backgroundColor': backgroundColor, if (fontSize != null) 'fontSize': fontSize, if (level != null) 'level': level, if (startTime != null) 'startTime': _formatTime(startTime!), if (endTime != null) 'endTime': _formatTime(endTime!), 'createTime': createTime?.toIso8601String(), 'updateTime': updateTime?.toIso8601String(), }; } /// 转换为 Web 后台可接收的字段名(用于新增/编辑请求)。 Map toWebJson() { return { if (id.isNotEmpty) 'id': int.tryParse(id), 'orgId': int.tryParse(storeId) ?? storeId, 'orgName': storeName, 'content': content, 'scrollSpeed': speed, if (fontColor != null) 'fontColor': fontColor, if (backgroundColor != null) 'bgColor': backgroundColor, if (fontSize != null) 'fontSize': fontSize, if (level != null) 'level': level, 'status': status, if (startTime != null) 'startTime': _formatTime(startTime!), if (endTime != null) 'endTime': _formatTime(endTime!), }; } MarqueeMessage copyWith({ String? id, String? storeId, String? storeName, String? content, int? status, int? speed, String? fontColor, String? backgroundColor, int? fontSize, int? level, DateTime? startTime, DateTime? endTime, DateTime? createTime, DateTime? updateTime, }) { return MarqueeMessage( id: id ?? this.id, storeId: storeId ?? this.storeId, storeName: storeName ?? this.storeName, content: content ?? this.content, status: status ?? this.status, speed: speed ?? this.speed, fontColor: fontColor ?? this.fontColor, backgroundColor: backgroundColor ?? this.backgroundColor, fontSize: fontSize ?? this.fontSize, level: level ?? this.level, startTime: startTime ?? this.startTime, endTime: endTime ?? this.endTime, createTime: createTime ?? this.createTime, updateTime: updateTime ?? this.updateTime, ); } /// 颜色字段兜底:当 Web 端未返回颜色时返回默认半透明色。 static const String defaultFontColor = '#FFFFFF'; static const String defaultBackgroundColor = '#3B82F6'; /// 兼容 `yyyy-MM-dd HH:mm:ss`、ISO 8601 以及纯 `HH:mm:ss` 时间字符串。 static DateTime? _parseDateTime(dynamic value) { if (value == null) return null; final text = value.toString().trim(); if (text.isEmpty) return null; // 纯时间:补齐一个固定日期用于本地 DateTime 存储。 if (RegExp(r'^\d{2}:\d{2}:\d{2}$').hasMatch(text)) { return DateTime.tryParse('2000-01-01 $text'); } return DateTime.tryParse(text) ?? DateTime.tryParse(text.replaceFirst(' ', 'T')); } /// 时间字段序列化为 `HH:mm:ss`,与 Web 后台保持一致。 static String _formatTime(DateTime value) { return '${value.hour.toString().padLeft(2, '0')}:' '${value.minute.toString().padLeft(2, '0')}:' '${value.second.toString().padLeft(2, '0')}'; } /// 解析整数字段,兼容 int / String / null。 static int? _parseInt(dynamic value, {int? fallback}) { if (value == null) return fallback; if (value is int) return value; if (value is String) return int.tryParse(value.trim()) ?? fallback; return fallback; } }